 


/*-------------------------------------------------------------------------
 *
 * createplan.c
 *	  创建处理查询所需计划的例程。
 *	  规划已经完成，我们只需要将选定的路径转换成计划。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/plan/createplan.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "access/sysattr.h"
#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/paramassign.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "partitioning/partprune.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"


/*
 * 可以出现在 create_plan_recurse() 的标志参数中的标志位。
 * 这些标志可以通过 OR 组合在一起。
 *
 * CP_EXACT_TLIST 指定生成的计划节点必须返回路径的 pathtarget 指定的 tlist（如果设置了 CP_SMALL_TLIST 和 CP_LABEL_TLIST，这将覆盖这两个标志）。
 * 否则，计划节点可以返回仅用于计算 pathtarget 所需的 Vars 和 PlaceHolderVars。
 *
 * CP_SMALL_TLIST 指定更窄的 tlist 是首选。这会被父节点（如 Sort 和 Hash）传递下去，它们必须存储返回的元组。
 *
 * CP_LABEL_TLIST 指定计划节点必须返回其 pathtarget 中指定的任何 sortgrouprefs 匹配的列，并带有适当的 ressortgroupref 标签。
 * 这将通过需要这些值在其输入中可用的父节点（例如 Sort 和 Group）传递下去。
 *
 * CP_IGNORE_TLIST 指定调用者计划替换目标列表，因此生成的目标列表毫无意义。
 */
#define CP_EXACT_TLIST		0x0001	/* 计划必须返回指定的 tlist */
#define CP_SMALL_TLIST		0x0002	/* 更倾向于更窄的 tlist */
#define CP_LABEL_TLIST		0x0004	/* tlist 必须包含 sortgrouprefs */
#define CP_IGNORE_TLIST		0x0008	/* 调用者将替换 tlist */


static Plan *fc_create_plan_recurse(PlannerInfo *fc_root, Path *fc_best_path,
								 int fc_flags);
static Plan *fc_create_scan_plan(PlannerInfo *fc_root, Path *fc_best_path,
							  int fc_flags);
static List *fc_build_path_tlist(PlannerInfo *fc_root, Path *fc_path);
static bool fc_use_physical_tlist(PlannerInfo *fc_root, Path *fc_path, int fc_flags);
static List *get_gating_quals(PlannerInfo *fc_root, List *fc_quals);
static Plan *fc_create_gating_plan(PlannerInfo *fc_root, Path *fc_path, Plan *fc_plan,
								List *fc_gating_quals);
static Plan *fc_create_join_plan(PlannerInfo *fc_root, JoinPath *fc_best_path);
static bool fc_mark_async_capable_plan(Plan *fc_plan, Path *fc_path);
static Plan *fc_create_append_plan(PlannerInfo *fc_root, AppendPath *fc_best_path,
								int fc_flags);
static Plan *fc_create_merge_append_plan(PlannerInfo *fc_root, MergeAppendPath *fc_best_path,
									  int fc_flags);
static Result *fc_create_group_result_plan(PlannerInfo *fc_root,
										GroupResultPath *fc_best_path);
static ProjectSet *fc_create_project_set_plan(PlannerInfo *fc_root, ProjectSetPath *fc_best_path);
static Material *fc_create_material_plan(PlannerInfo *fc_root, MaterialPath *fc_best_path,
									  int fc_flags);
static Memoize *fc_create_memoize_plan(PlannerInfo *fc_root, MemoizePath *fc_best_path,
									int fc_flags);
static Plan *fc_create_unique_plan(PlannerInfo *fc_root, UniquePath *fc_best_path,
								int fc_flags);
static Gather *fc_create_gather_plan(PlannerInfo *fc_root, GatherPath *fc_best_path);
static Plan *fc_create_projection_plan(PlannerInfo *fc_root,
									ProjectionPath *fc_best_path,
									int fc_flags);
static Plan *fc_inject_projection_plan(Plan *fc_subplan, List *fc_tlist, bool fc_parallel_safe);
static Sort *fc_create_sort_plan(PlannerInfo *fc_root, SortPath *fc_best_path, int fc_flags);
static IncrementalSort *fc_create_incrementalsort_plan(PlannerInfo *fc_root,
													IncrementalSortPath *fc_best_path, int fc_flags);
static Group *fc_create_group_plan(PlannerInfo *fc_root, GroupPath *fc_best_path);
static Unique *fc_create_upper_unique_plan(PlannerInfo *fc_root, UpperUniquePath *fc_best_path,
										int fc_flags);
static Agg *fc_create_agg_plan(PlannerInfo *fc_root, AggPath *fc_best_path);
static Plan *fc_create_groupingsets_plan(PlannerInfo *fc_root, GroupingSetsPath *fc_best_path);
static Result *fc_create_minmaxagg_plan(PlannerInfo *fc_root, MinMaxAggPath *fc_best_path);
static WindowAgg *fc_create_windowagg_plan(PlannerInfo *fc_root, WindowAggPath *fc_best_path);
static SetOp *fc_create_setop_plan(PlannerInfo *fc_root, SetOpPath *fc_best_path,
								int fc_flags);
static RecursiveUnion *fc_create_recursiveunion_plan(PlannerInfo *fc_root, RecursiveUnionPath *fc_best_path);
static LockRows *fc_create_lockrows_plan(PlannerInfo *fc_root, LockRowsPath *fc_best_path,
									  int fc_flags);
static ModifyTable *fc_create_modifytable_plan(PlannerInfo *fc_root, ModifyTablePath *fc_best_path);
static Limit *fc_create_limit_plan(PlannerInfo *fc_root, LimitPath *fc_best_path,
								int fc_flags);
static SeqScan *fc_create_seqscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
									List *fc_tlist, List *fc_scan_clauses);
static SampleScan *fc_create_samplescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
										  List *fc_tlist, List *fc_scan_clauses);
static Scan *fc_create_indexscan_plan(PlannerInfo *fc_root, IndexPath *fc_best_path,
								   List *fc_tlist, List *fc_scan_clauses, bool fc_indexonly);
static BitmapHeapScan *fc_create_bitmap_scan_plan(PlannerInfo *fc_root,
											   BitmapHeapPath *fc_best_path,
											   List *fc_tlist, List *fc_scan_clauses);
static Plan *fc_create_bitmap_subplan(PlannerInfo *fc_root, Path *fc_bitmapqual,
								   List **fc_qual, List **fc_indexqual, List **fc_indexECs);
static void fc_bitmap_subplan_mark_shared(Plan *fc_plan);
static TidScan *fc_create_tidscan_plan(PlannerInfo *fc_root, TidPath *fc_best_path,
									List *fc_tlist, List *fc_scan_clauses);
static TidRangeScan *fc_create_tidrangescan_plan(PlannerInfo *fc_root,
											  TidRangePath *fc_best_path,
											  List *fc_tlist,
											  List *fc_scan_clauses);
static SubqueryScan *fc_create_subqueryscan_plan(PlannerInfo *fc_root,
											  SubqueryScanPath *fc_best_path,
											  List *fc_tlist, List *fc_scan_clauses);
static FunctionScan *fc_create_functionscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
											  List *fc_tlist, List *fc_scan_clauses);
static ValuesScan *fc_create_valuesscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
										  List *fc_tlist, List *fc_scan_clauses);
static TableFuncScan *fc_create_tablefuncscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
												List *fc_tlist, List *fc_scan_clauses);
static CteScan *fc_create_ctescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
									List *fc_tlist, List *fc_scan_clauses);
static NamedTuplestoreScan *fc_create_namedtuplestorescan_plan(PlannerInfo *fc_root,
															Path *fc_best_path, List *fc_tlist, List *fc_scan_clauses);
static Result *fc_create_resultscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
									  List *fc_tlist, List *fc_scan_clauses);
static WorkTableScan *fc_create_worktablescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
												List *fc_tlist, List *fc_scan_clauses);
static ForeignScan *fc_create_foreignscan_plan(PlannerInfo *fc_root, ForeignPath *fc_best_path,
											List *fc_tlist, List *fc_scan_clauses);
static CustomScan *fc_create_customscan_plan(PlannerInfo *fc_root,
										  CustomPath *fc_best_path,
										  List *fc_tlist, List *fc_scan_clauses);
static NestLoop *fc_create_nestloop_plan(PlannerInfo *fc_root, NestPath *fc_best_path);
static MergeJoin *fc_create_mergejoin_plan(PlannerInfo *fc_root, MergePath *fc_best_path);
static HashJoin *fc_create_hashjoin_plan(PlannerInfo *fc_root, HashPath *fc_best_path);
static Node *fc_replace_nestloop_params(PlannerInfo *fc_root, Node *fc_expr);
static Node *fc_replace_nestloop_params_mutator(Node *fc_node, PlannerInfo *fc_root);
static void fc_fix_indexqual_references(PlannerInfo *fc_root, IndexPath *fc_index_path,
									 List **fc_stripped_indexquals_p,
									 List **fc_fixed_indexquals_p);
static List *fc_fix_indexorderby_references(PlannerInfo *fc_root, IndexPath *fc_index_path);
static Node *fc_fix_indexqual_clause(PlannerInfo *fc_root,
								  IndexOptInfo *fc_index, int fc_indexcol,
								  Node *fc_clause, List *fc_indexcolnos);
static Node *fc_fix_indexqual_operand(Node *fc_node, IndexOptInfo *fc_index, int fc_indexcol);
static List *get_switched_clauses(List *fc_clauses, Relids fc_outerrelids);
static List *fc_order_qual_clauses(PlannerInfo *fc_root, List *fc_clauses);
static void fc_copy_generic_path_info(Plan *fc_dest, Path *fc_src);
static void fc_copy_plan_costsize(Plan *fc_dest, Plan *fc_src);
static void fc_label_sort_with_costsize(PlannerInfo *fc_root, Sort *fc_plan,
									 double fc_limit_tuples);
static SeqScan *fc_make_seqscan(List *fc_qptlist, List *fc_qpqual, Index fc_scanrelid);
static SampleScan *fc_make_samplescan(List *fc_qptlist, List *fc_qpqual, Index fc_scanrelid,
								   TableSampleClause *fc_tsc);
static IndexScan *fc_make_indexscan(List *fc_qptlist, List *fc_qpqual, Index fc_scanrelid,
								 Oid fc_indexid, List *fc_indexqual, List *fc_indexqualorig,
								 List *fc_indexorderby, List *fc_indexorderbyorig,
								 List *fc_indexorderbyops,
								 ScanDirection fc_indexscandir);
static IndexOnlyScan *fc_make_indexonlyscan(List *fc_qptlist, List *fc_qpqual,
										 Index fc_scanrelid, Oid fc_indexid,
										 List *fc_indexqual, List *fc_recheckqual,
										 List *fc_indexorderby,
										 List *fc_indextlist,
										 ScanDirection fc_indexscandir);
static BitmapIndexScan *fc_make_bitmap_indexscan(Index fc_scanrelid, Oid fc_indexid,
											  List *fc_indexqual,
											  List *fc_indexqualorig);
static BitmapHeapScan *fc_make_bitmap_heapscan(List *fc_qptlist,
											List *fc_qpqual,
											Plan *fc_lefttree,
											List *fc_bitmapqualorig,
											Index fc_scanrelid);
static TidScan *fc_make_tidscan(List *fc_qptlist, List *fc_qpqual, Index fc_scanrelid,
							 List *fc_tidquals);
static TidRangeScan *fc_make_tidrangescan(List *fc_qptlist, List *fc_qpqual,
									   Index fc_scanrelid, List *fc_tidrangequals);
static SubqueryScan *fc_make_subqueryscan(List *fc_qptlist,
									   List *fc_qpqual,
									   Index fc_scanrelid,
									   Plan *fc_subplan);
static FunctionScan *fc_make_functionscan(List *fc_qptlist, List *fc_qpqual,
									   Index fc_scanrelid, List *fc_functions, bool fc_funcordinality);
static ValuesScan *fc_make_valuesscan(List *fc_qptlist, List *fc_qpqual,
								   Index fc_scanrelid, List *fc_values_lists);
static TableFuncScan *fc_make_tablefuncscan(List *fc_qptlist, List *fc_qpqual,
										 Index fc_scanrelid, TableFunc *fc_tablefunc);
static CteScan *fc_make_ctescan(List *fc_qptlist, List *fc_qpqual,
							 Index fc_scanrelid, int fc_ctePlanId, int fc_cteParam);
static NamedTuplestoreScan *fc_make_namedtuplestorescan(List *fc_qptlist, List *fc_qpqual,
													 Index fc_scanrelid, char *fc_enrname);
static WorkTableScan *fc_make_worktablescan(List *fc_qptlist, List *fc_qpqual,
										 Index fc_scanrelid, int fc_wtParam);
static RecursiveUnion *fc_make_recursive_union(List *fc_tlist,
											Plan *fc_lefttree,
											Plan *fc_righttree,
											int fc_wtParam,
											List *fc_distinctList,
											long fc_numGroups);
static BitmapAnd *fc_make_bitmap_and(List *fc_bitmapplans);
static BitmapOr *fc_make_bitmap_or(List *fc_bitmapplans);
static NestLoop *fc_make_nestloop(List *fc_tlist,
							   List *fc_joinclauses, List *fc_otherclauses, List *fc_nestParams,
							   Plan *fc_lefttree, Plan *fc_righttree,
							   JoinType fc_jointype, bool fc_inner_unique);
static HashJoin *fc_make_hashjoin(List *fc_tlist,
							   List *fc_joinclauses, List *fc_otherclauses,
							   List *fc_hashclauses,
							   List *fc_hashoperators, List *fc_hashcollations,
							   List *fc_hashkeys,
							   Plan *fc_lefttree, Plan *fc_righttree,
							   JoinType fc_jointype, bool fc_inner_unique);
static Hash *fc_make_hash(Plan *fc_lefttree,
					   List *fc_hashkeys,
					   Oid fc_skewTable,
					   AttrNumber fc_skewColumn,
					   bool fc_skewInherit);
static MergeJoin *fc_make_mergejoin(List *fc_tlist,
								 List *fc_joinclauses, List *fc_otherclauses,
								 List *fc_mergeclauses,
								 Oid *fc_mergefamilies,
								 Oid *fc_mergecollations,
								 int *fc_mergestrategies,
								 bool *fc_mergenullsfirst,
								 Plan *fc_lefttree, Plan *fc_righttree,
								 JoinType fc_jointype, bool fc_inner_unique,
								 bool fc_skip_mark_restore);
static Sort *fc_make_sort(Plan *fc_lefttree, int fc_numCols,
					   AttrNumber *fc_sortColIdx, Oid *fc_sortOperators,
					   Oid *fc_collations, bool *fc_nullsFirst);
static IncrementalSort *fc_make_incrementalsort(Plan *fc_lefttree,
											 int fc_numCols, int fc_nPresortedCols,
											 AttrNumber *fc_sortColIdx, Oid *fc_sortOperators,
											 Oid *fc_collations, bool *fc_nullsFirst);
static Plan *fc_prepare_sort_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys,
										Relids fc_relids,
										const AttrNumber *fc_reqColIdx,
										bool fc_adjust_tlist_in_place,
										int *fc_p_numsortkeys,
										AttrNumber **fc_p_sortColIdx,
										Oid **fc_p_sortOperators,
										Oid **fc_p_collations,
										bool **fc_p_nullsFirst);
static Sort *fc_make_sort_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys,
									 Relids fc_relids);
static IncrementalSort *fc_make_incrementalsort_from_pathkeys(Plan *fc_lefttree,
														   List *fc_pathkeys, Relids fc_relids, int fc_nPresortedCols);
static Sort *fc_make_sort_from_groupcols(List *fc_groupcls,
									  AttrNumber *fc_grpColIdx,
									  Plan *fc_lefttree);
static Material *fc_make_material(Plan *fc_lefttree);
static Memoize *fc_make_memoize(Plan *fc_lefttree, Oid *fc_hashoperators,
							 Oid *fc_collations, List *fc_param_exprs,
							 bool fc_singlerow, bool fc_binary_mode,
							 uint32 fc_est_entries, Bitmapset *fc_keyparamids);
static WindowAgg *fc_make_windowagg(List *fc_tlist, Index fc_winref,
								 int fc_partNumCols, AttrNumber *fc_partColIdx, Oid *fc_partOperators, Oid *fc_partCollations,
								 int fc_ordNumCols, AttrNumber *fc_ordColIdx, Oid *fc_ordOperators, Oid *fc_ordCollations,
								 int fc_frameOptions, Node *fc_startOffset, Node *fc_endOffset,
								 Oid fc_startInRangeFunc, Oid fc_endInRangeFunc,
								 Oid fc_inRangeColl, bool fc_inRangeAsc, bool fc_inRangeNullsFirst,
								 List *fc_runCondition, List *fc_qual, bool fc_topWindow,
								 Plan *fc_lefttree);
static Group *fc_make_group(List *fc_tlist, List *fc_qual, int fc_numGroupCols,
						 AttrNumber *fc_grpColIdx, Oid *fc_grpOperators, Oid *fc_grpCollations,
						 Plan *fc_lefttree);
static Unique *fc_make_unique_from_sortclauses(Plan *fc_lefttree, List *fc_distinctList);
static Unique *fc_make_unique_from_pathkeys(Plan *fc_lefttree,
										 List *fc_pathkeys, int fc_numCols);
static Gather *fc_make_gather(List *fc_qptlist, List *fc_qpqual,
						   int fc_nworkers, int fc_rescan_param, bool fc_single_copy, Plan *fc_subplan);
static SetOp *fc_make_setop(SetOpCmd fc_cmd, SetOpStrategy fc_strategy, Plan *fc_lefttree,
						 List *fc_distinctList, AttrNumber fc_flagColIdx, int fc_firstFlag,
						 long fc_numGroups);
static LockRows *fc_make_lockrows(Plan *fc_lefttree, List *fc_rowMarks, int fc_epqParam);
static Result *fc_make_result(List *fc_tlist, Node *fc_resconstantqual, Plan *fc_subplan);
static ProjectSet *fc_make_project_set(List *fc_tlist, Plan *fc_subplan);
static ModifyTable *fc_make_modifytable(PlannerInfo *fc_root, Plan *fc_subplan,
									 CmdType fc_operation, bool fc_canSetTag,
									 Index fc_nominalRelation, Index fc_rootRelation,
									 bool fc_partColsUpdated,
									 List *fc_resultRelations,
									 List *fc_updateColnosLists,
									 List *fc_withCheckOptionLists, List *fc_returningLists,
									 List *fc_rowMarks, OnConflictExpr *fc_onconflict,
									 List *fc_mergeActionList, int fc_epqParam);
static GatherMerge *fc_create_gather_merge_plan(PlannerInfo *fc_root,
											 GatherMergePath *fc_best_path);


/*
 * create_plan
 *	  通过递归处理所需的 pathnodes 树，从节点 'best_path' 开始，为查询创建访问计划。
 *	  对于找到的每个 pathnode，我们创建一个相应的计划节点，包含适当的 id、目标列表和资格信息。
 *
 *	  计划树中的 tlists 和 quals 仍然是规划器格式，即，Vars 仍然对应于解析器的编号。
 *	  这将在稍后通过 setrefs.c 修复。
 *
 *	  best_path 是最佳访问路径。
 *
 *	  返回一个计划树。
 */
Plan * create_plan(PlannerInfo *fc_root, Path *fc_best_path)
{
	Plan	   *fc_plan;

	/* plan_params 不应该在当前查询级别中使用 */
	Assert(fc_root->plan_params == NIL);

	/* 在 PlannerInfo 中初始化该模块的工作区 */
	fc_root->curOuterRels = NULL;
	fc_root->curOuterParams = NIL;

	/* 递归处理路径树，要求正确的 tlist 结果 */
	fc_plan = fc_create_plan_recurse(fc_root, fc_best_path, CP_EXACT_TLIST);

	/*
	 * 确保最上层计划节点的目标列表暴露原始列名称和其他装饰信息。
	 * 在规划器内部生成的目标列表不处理这些信息，但在执行时我们必须在
	 * 最上层 tlist 中拥有它。然而，ModifyTable 计划节点没有与
	 * 查询树目标列表匹配的 tlist。
	 */
	if (!IsA(fc_plan, ModifyTable))
		apply_tlist_labeling(fc_plan->targetlist, fc_root->processed_tlist);

	/*
	 * 将此查询级别中创建的任何 initPlans 附加到最上层计划节点。
	 * （原则上 initplans 可以放在任何计划节点中，或在它们被引用的上方，
	 * 但似乎没有理由将它们放在查询级别的最上层节点以下。
	 * 此外，在尝试更改此内容之前，请查看 SS_finalize_plan 的注释。）
	 */
	SS_attach_initplans(fc_root, fc_plan);

	/* 检查我们是否成功分配了所有 NestLoopParams 到计划节点 */
	if (fc_root->curOuterParams != NIL)
		elog(ERROR, "failed to assign all NestLoopParams to plan nodes");

	/*
	 * 重置 plan_params，以确保用于 nestloop 参数的参数 ID 不会
	 * 被后续重用。
	 */
	fc_root->plan_params = NIL;

	return fc_plan;
}

/*
 * create_plan_recurse
 *	  create_plan() 的递归实现。
 */
static Plan * fc_create_plan_recurse(PlannerInfo *fc_root, Path *fc_best_path, int fc_flags)
{
	Plan	   *fc_plan;

	/* 防止由于过于复杂的计划导致堆栈溢出 */
	check_stack_depth();

	switch (fc_best_path->pathtype)
	{
		case T_SeqScan:
		case T_SampleScan:
		case T_IndexScan:
		case T_IndexOnlyScan:
		case T_BitmapHeapScan:
		case T_TidScan:
		case T_TidRangeScan:
		case T_SubqueryScan:
		case T_FunctionScan:
		case T_TableFuncScan:
		case T_ValuesScan:
		case T_CteScan:
		case T_WorkTableScan:
		case T_NamedTuplestoreScan:
		case T_ForeignScan:
		case T_CustomScan:
			fc_plan = fc_create_scan_plan(fc_root, fc_best_path, fc_flags);
			break;
		case T_HashJoin:
		case T_MergeJoin:
		case T_NestLoop:
			fc_plan = fc_create_join_plan(fc_root,
									(JoinPath *) fc_best_path);
			break;
		case T_Append:
			fc_plan = fc_create_append_plan(fc_root,
									  (AppendPath *) fc_best_path,
									  fc_flags);
			break;
		case T_MergeAppend:
			fc_plan = fc_create_merge_append_plan(fc_root,
											(MergeAppendPath *) fc_best_path,
											fc_flags);
			break;
		case T_Result:
			if (IsA(fc_best_path, ProjectionPath))
			{
				fc_plan = fc_create_projection_plan(fc_root,
											  (ProjectionPath *) fc_best_path,
											  fc_flags);
			}
			else if (IsA(fc_best_path, MinMaxAggPath))
			{
				fc_plan = (Plan *) fc_create_minmaxagg_plan(fc_root,
													  (MinMaxAggPath *) fc_best_path);
			}
			else if (IsA(fc_best_path, GroupResultPath))
			{
				fc_plan = (Plan *) fc_create_group_result_plan(fc_root,
														 (GroupResultPath *) fc_best_path);
			}
			else
			{
				/* 简单的 RTE_RESULT 基本关系 */
				Assert(IsA(fc_best_path, Path));
				fc_plan = fc_create_scan_plan(fc_root, fc_best_path, fc_flags);
			}
			break;
		case T_ProjectSet:
			fc_plan = (Plan *) fc_create_project_set_plan(fc_root,
													(ProjectSetPath *) fc_best_path);
			break;
		case T_Material:
			fc_plan = (Plan *) fc_create_material_plan(fc_root,
												 (MaterialPath *) fc_best_path,
												 fc_flags);
			break;
		case T_Memoize:
			fc_plan = (Plan *) fc_create_memoize_plan(fc_root,
												(MemoizePath *) fc_best_path,
												fc_flags);
			break;
		case T_Unique:
			if (IsA(fc_best_path, UpperUniquePath))
			{
				fc_plan = (Plan *) fc_create_upper_unique_plan(fc_root,
														 (UpperUniquePath *) fc_best_path,
														 fc_flags);
			}
			else
			{
				Assert(IsA(fc_best_path, UniquePath));
				fc_plan = fc_create_unique_plan(fc_root,
										  (UniquePath *) fc_best_path,
										  fc_flags);
			}
			break;
		case T_Gather:
			fc_plan = (Plan *) fc_create_gather_plan(fc_root,
											   (GatherPath *) fc_best_path);
			break;
		case T_Sort:
			fc_plan = (Plan *) fc_create_sort_plan(fc_root,
											 (SortPath *) fc_best_path,
											 fc_flags);
			break;
		case T_IncrementalSort:
			fc_plan = (Plan *) fc_create_incrementalsort_plan(fc_root,
														(IncrementalSortPath *) fc_best_path,
														fc_flags);
			break;
		case T_Group:
			fc_plan = (Plan *) fc_create_group_plan(fc_root,
											  (GroupPath *) fc_best_path);
			break;
		case T_Agg:
			if (IsA(fc_best_path, GroupingSetsPath))
				fc_plan = fc_create_groupingsets_plan(fc_root,
												(GroupingSetsPath *) fc_best_path);
			else
			{
				Assert(IsA(fc_best_path, AggPath));
				fc_plan = (Plan *) fc_create_agg_plan(fc_root,
												(AggPath *) fc_best_path);
			}
			break;
		case T_WindowAgg:
			fc_plan = (Plan *) fc_create_windowagg_plan(fc_root,
												  (WindowAggPath *) fc_best_path);
			break;
		case T_SetOp:
			fc_plan = (Plan *) fc_create_setop_plan(fc_root,
											  (SetOpPath *) fc_best_path,
											  fc_flags);
			break;
		case T_RecursiveUnion:
			fc_plan = (Plan *) fc_create_recursiveunion_plan(fc_root,
													   (RecursiveUnionPath *) fc_best_path);
			break;
		case T_LockRows:
			fc_plan = (Plan *) fc_create_lockrows_plan(fc_root,
												 (LockRowsPath *) fc_best_path,
												 fc_flags);
			break;
		case T_ModifyTable:
			fc_plan = (Plan *) fc_create_modifytable_plan(fc_root,
													(ModifyTablePath *) fc_best_path);
			break;
		case T_Limit:
			fc_plan = (Plan *) fc_create_limit_plan(fc_root,
											  (LimitPath *) fc_best_path,
											  fc_flags);
			break;
		case T_GatherMerge:
			fc_plan = (Plan *) fc_create_gather_merge_plan(fc_root,
													 (GatherMergePath *) fc_best_path);
			break;
		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) fc_best_path->pathtype);
			fc_plan = NULL;		/* 保持编译器安静 */
			break;
	}

	return fc_plan;
}

/*
 * create_scan_plan
 *	 为 'best_path' 的父关系创建扫描计划。
 */
static Plan * fc_create_scan_plan(PlannerInfo *fc_root, Path *fc_best_path, int fc_flags)
{
	RelOptInfo *fc_rel = fc_best_path->parent;
	List	   *fc_scan_clauses;
	List	   *fc_gating_clauses;
	List	   *fc_tlist;
	Plan	   *fc_plan;

	/*
	 * 从父关系中提取相关的限制子句。执行器必须在扫描过程中应用所有这些限制，
	 * 除了伪常量，我们将在下面处理。
	 *
	 * 如果这是一个普通的索引扫描或仅索引扫描，则我们不需要考虑由索引的谓词
	 * 隐含的限制子句，因此使用 indrestrictinfo 而不是 baserestrictinfo。
	 * 请注意，我们无法对位图索引扫描执行此操作，因为不一定涉及单个索引；
	 * 但这无关紧要，因为 create_bitmap_scan_plan() 将能够通过谓词证明
	 * 消除这些子句。
	 */
	switch (fc_best_path->pathtype)
	{
		case T_IndexScan:
		case T_IndexOnlyScan:
			fc_scan_clauses = castNode(IndexPath, fc_best_path)->indexinfo->indrestrictinfo;
			break;
		default:
			fc_scan_clauses = fc_rel->baserestrictinfo;
			break;
	}

	/*
	 * 如果这是一个参数化扫描，我们还需要强制执行来自外部关系的所有连接子句。
	 *
	 * 出于谨慎起见，请勿修改存储的 baserestrictinfo 列表。
	 */
	if (fc_best_path->param_info)
		fc_scan_clauses = list_concat_copy(fc_scan_clauses,
										fc_best_path->param_info->ppi_clauses);

	/*
	 * 检测我们是否有任何伪常量的资格需要处理。如果我们需要一个门控结果节点，
	 * 它将能够进行投影，因此对子节点的 tlist 没有要求。
	 */
	fc_gating_clauses = get_gating_quals(fc_root, fc_scan_clauses);
	if (fc_gating_clauses)
		fc_flags = 0;

	/*
	 * 对于表扫描，与其使用关系目标列表（其中仅包含查询实际需要的 Vars），
	 * 我们更愿意生成一个包含所有 Vars 的 tlist。这样将允许执行器在可能的情况下
	 * 优化掉表元组的投影。
	 *
	 * 但如果调用者将无论如何忽略我们的 tlist，那么就根本不必生成一个。
	 * 我们在这里使用精确的相等性测试，因此这只适用于 CP_IGNORE_TLIST
	 * 是唯一设置的标志的情况。
	 */
	if (fc_flags == CP_IGNORE_TLIST)
	{
		fc_tlist = NULL;
	}
	else if (fc_use_physical_tlist(fc_root, fc_best_path, fc_flags))
	{
		if (fc_best_path->pathtype == T_IndexOnlyScan)
		{
			/* 对于仅索引扫描，首选的 tlist 是索引的 */
			fc_tlist = copyObject(((IndexPath *) fc_best_path)->indexinfo->indextlist);

			/*
			 * 如果请求，将 sortgroupref 数据转移到替换 tlist 中（use_physical_tlist
			 * 检查过这将有效）。
			 */
			if (fc_flags & CP_LABEL_TLIST)
				apply_pathtarget_labeling_to_tlist(fc_tlist, fc_best_path->pathtarget);
		}
		else
		{
			fc_tlist = build_physical_tlist(fc_root, fc_rel);
			if (fc_tlist == NIL)
			{
				/* 由于丢弃的列而失败，因此使用常规方法 */
				fc_tlist = fc_build_path_tlist(fc_root, fc_best_path);
			}
			else
			{
				/* 同上，将 sortgroupref 数据转移到替换 tlist */
				if (fc_flags & CP_LABEL_TLIST)
					apply_pathtarget_labeling_to_tlist(fc_tlist, fc_best_path->pathtarget);
			}
		}
	}
	else
	{
		fc_tlist = fc_build_path_tlist(fc_root, fc_best_path);
	}

	switch (fc_best_path->pathtype)
	{
		case T_SeqScan:
			fc_plan = (Plan *) fc_create_seqscan_plan(fc_root,
												fc_best_path,
												fc_tlist,
												fc_scan_clauses);
			break;

		case T_SampleScan:
			fc_plan = (Plan *) fc_create_samplescan_plan(fc_root,
												   fc_best_path,
												   fc_tlist,
												   fc_scan_clauses);
			break;

		case T_IndexScan:
			fc_plan = (Plan *) fc_create_indexscan_plan(fc_root,
												  (IndexPath *) fc_best_path,
												  fc_tlist,
												  fc_scan_clauses,
												  false);
			break;

		case T_IndexOnlyScan:
			fc_plan = (Plan *) fc_create_indexscan_plan(fc_root,
												  (IndexPath *) fc_best_path,
												  fc_tlist,
												  fc_scan_clauses,
												  true);
			break;

		case T_BitmapHeapScan:
			fc_plan = (Plan *) fc_create_bitmap_scan_plan(fc_root,
													(BitmapHeapPath *) fc_best_path,
													fc_tlist,
													fc_scan_clauses);
			break;

		case T_TidScan:
			fc_plan = (Plan *) fc_create_tidscan_plan(fc_root,
												(TidPath *) fc_best_path,
												fc_tlist,
												fc_scan_clauses);
			break;

		case T_TidRangeScan:
			fc_plan = (Plan *) fc_create_tidrangescan_plan(fc_root,
													 (TidRangePath *) fc_best_path,
													 fc_tlist,
													 fc_scan_clauses);
			break;

		case T_SubqueryScan:
			fc_plan = (Plan *) fc_create_subqueryscan_plan(fc_root,
													 (SubqueryScanPath *) fc_best_path,
													 fc_tlist,
													 fc_scan_clauses);
			break;

		case T_FunctionScan:
			fc_plan = (Plan *) fc_create_functionscan_plan(fc_root,
													 fc_best_path,
													 fc_tlist,
													 fc_scan_clauses);
			break;

		case T_TableFuncScan:
			fc_plan = (Plan *) fc_create_tablefuncscan_plan(fc_root,
													  fc_best_path,
													  fc_tlist,
													  fc_scan_clauses);
			break;

		case T_ValuesScan:
			fc_plan = (Plan *) fc_create_valuesscan_plan(fc_root,
												   fc_best_path,
												   fc_tlist,
												   fc_scan_clauses);
			break;

		case T_CteScan:
			fc_plan = (Plan *) fc_create_ctescan_plan(fc_root,
												fc_best_path,
												fc_tlist,
												fc_scan_clauses);
			break;

		case T_NamedTuplestoreScan:
			fc_plan = (Plan *) fc_create_namedtuplestorescan_plan(fc_root,
															fc_best_path,
															fc_tlist,
															fc_scan_clauses);
			break;

		case T_Result:
			fc_plan = (Plan *) fc_create_resultscan_plan(fc_root,
												   fc_best_path,
												   fc_tlist,
												   fc_scan_clauses);
			break;

		case T_WorkTableScan:
			fc_plan = (Plan *) fc_create_worktablescan_plan(fc_root,
													  fc_best_path,
													  fc_tlist,
													  fc_scan_clauses);
			break;

		case T_ForeignScan:
			fc_plan = (Plan *) fc_create_foreignscan_plan(fc_root,
													(ForeignPath *) fc_best_path,
													fc_tlist,
													fc_scan_clauses);
			break;

		case T_CustomScan:
			fc_plan = (Plan *) fc_create_customscan_plan(fc_root,
												   (CustomPath *) fc_best_path,
												   fc_tlist,
												   fc_scan_clauses);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) fc_best_path->pathtype);
			fc_plan = NULL;		/* 保持编译器安静 */
			break;
	}

	/*
	 * 如果此节点附加有任何伪常量子句，请插入一个评估伪常量作为一次性
	 * 资格的门控结果节点。
	 */
	if (fc_gating_clauses)
		fc_plan = fc_create_gating_plan(fc_root, fc_best_path, fc_plan, fc_gating_clauses);

	return fc_plan;
}

/*
 * 为路径的输出构建目标列表（即，TargetEntry 列表）。
 *
 * 这几乎就是 make_tlist_from_pathtarget()，但我们还必须处理替换 nestloop 参数。
 */
static List * fc_build_path_tlist(PlannerInfo *fc_root, Path *fc_path)
{
	List	   *fc_tlist = NIL;
	Index	   *fc_sortgrouprefs = fc_path->pathtarget->sortgrouprefs;
	int			fc_resno = 1;
	ListCell   *fc_v;

	foreach(fc_v, fc_path->pathtarget->exprs)
	{
		Node	   *fc_node = (Node *) lfirst(fc_v);
		TargetEntry *fc_tle;

		/*
		 * 如果这是一个参数化路径，tlist 中可能有横向引用，
		 * 需要用参数替换。无需重新制作 TargetEntry 节点，
		 * 因此将此应用于每个列表项。
		 */
		if (fc_path->param_info)
			fc_node = fc_replace_nestloop_params(fc_root, fc_node);

		fc_tle = makeTargetEntry((Expr *) fc_node,
							  fc_resno,
							  NULL,
							  false);
		if (fc_sortgrouprefs)
			fc_tle->ressortgroupref = fc_sortgrouprefs[fc_resno - 1];

		fc_tlist = lappend(fc_tlist, fc_tle);
		fc_resno++;
	}
	return fc_tlist;
}

/*
 * use_physical_tlist
 *		决定是否使用与关系结构匹配的 tlist，
 *		而不是仅使用实际引用的 Vars。
 */
static bool fc_use_physical_tlist(PlannerInfo *fc_root, Path *fc_path, int fc_flags)
{
	RelOptInfo *fc_rel = fc_path->parent;
	int			fc_i;
	ListCell   *fc_lc;

	/*
	 * 如果要求使用精确 tlist 或小 tlist，则忽略它。
	 */
	if (fc_flags & (CP_EXACT_TLIST | CP_SMALL_TLIST))
		return false;

	/*
	 * 我们可以对真实关系扫描、子查询扫描、函数扫描、
	 * tablefunc 扫描、值扫描和 CTE 扫描执行此操作（但不能对诸如连接等操作）。
	 */
	if (fc_rel->rtekind != RTE_RELATION &&
		fc_rel->rtekind != RTE_SUBQUERY &&
		fc_rel->rtekind != RTE_FUNCTION &&
		fc_rel->rtekind != RTE_TABLEFUNC &&
		fc_rel->rtekind != RTE_VALUES &&
		fc_rel->rtekind != RTE_CTE)
		return false;

	
/*
	 * 也无法处理继承案例（主要是因为 Append
	 * 不进行投影；鉴于 create_append_plan 指示其子节点返回一个准确的 tlist，这项测试可能不再必要）。
	 */
	if (fc_rel->reloptkind != RELOPT_BASEREL)
		return false;

	/*
	 * 另外，不要对 CustomPath 这样做；我们从一个简单的物理元组中提取列的前提对于这些情况不太可能成立。
	 * （当这合理时，自定义路径创建者可以以这种方式设置路径的
	 * pathtarget。）
	 */
	if (IsA(fc_path, CustomPath))
		return false;

	/*
	 * 如果位图扫描的 tlist 为空，则保持原样。这可能允许执行器跳过堆页面提取，并且在任何情况下，使用物理 tlist 的好处将是微乎其微的。
	 */
	if (IsA(fc_path, BitmapHeapPath) &&
		fc_path->pathtarget->exprs == NIL)
		return false;

	/*
	 * 如果请求了任何系统列或整行变量，则无法执行此操作。
	 * （这可能可以修复，但我认为这需要在 setrefs.c 中进行一些脆弱的假设。）
	 */
	for (fc_i = fc_rel->min_attr; fc_i <= 0; fc_i++)
	{
		if (!bms_is_empty(fc_rel->attr_needed[fc_i - fc_rel->min_attr]))
			return false;
	}

	/*
	 * 如果需要发出任何占位符表达式，则无法做到这一点。
	 */
	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);

		if (bms_nonempty_difference(fc_phinfo->ph_needed, fc_rel->relids) &&
			bms_is_subset(fc_phinfo->ph_eval_at, fc_rel->relids))
			return false;
	}

	/*
	 * 对于索引仅扫描，“物理 tlist”是索引的 indextlist。
	 * 我们只在所有索引的列都可以返回的情况下才能不使用投影而返回该列表。
	 */
	if (fc_path->pathtype == T_IndexOnlyScan)
	{
		IndexOptInfo *fc_indexinfo = ((IndexPath *) fc_path)->indexinfo;

		for (fc_i = 0; fc_i < fc_indexinfo->ncolumns; fc_i++)
		{
			if (!fc_indexinfo->canreturn[fc_i])
				return false;
		}
	}

	/*
	 * 同样，如果指定了 CP_LABEL_TLIST，并且请求路径发出任何非简单 Vars 的排序/分组列，则无法做到这一点。
	 * （如果它们是简单 Vars，则它们应该出现在物理 tlist 中，
	 * apply_pathtarget_labeling_to_tlist 将负责再次标记它们。）
	 * 我们还必须检查是否有两个排序/分组列是相同的 Var，否则物理 tlist 的该元素将需要冲突的 ressortgroupref 标签。
	 */
	if ((fc_flags & CP_LABEL_TLIST) && fc_path->pathtarget->sortgrouprefs)
	{
		Bitmapset  *fc_sortgroupatts = NULL;

		fc_i = 0;
		foreach(fc_lc, fc_path->pathtarget->exprs)
		{
			Expr	   *fc_expr = (Expr *) lfirst(fc_lc);

			if (fc_path->pathtarget->sortgrouprefs[fc_i])
			{
				if (fc_expr && IsA(fc_expr, Var))
				{
					int			fc_attno = ((Var *) fc_expr)->varattno;

					fc_attno -= FirstLowInvalidHeapAttributeNumber;
					if (bms_is_member(fc_attno, fc_sortgroupatts))
						return false;
					fc_sortgroupatts = bms_add_member(fc_sortgroupatts, fc_attno);
				}
				else
					return false;
			}
			fc_i++;
		}
	}

	return true;
}

/*
 * get_gating_quals
 *	  查看节点的 quals 列表中是否存在伪常量 quals
 *
 * 如果节点的 quals 列表中包含任何伪常量 quals，
 * 则仅返回这些 quals。
 */
static List * get_gating_quals(PlannerInfo *fc_root, List *fc_quals)
{
	/* 如果我们知道没有伪常量，则无需查看 */
	if (!fc_root->hasPseudoConstantQuals)
		return NIL;

	/* 在仍处于 RestrictInfo 形式时按期望执行顺序排序 */
	fc_quals = fc_order_qual_clauses(fc_root, fc_quals);

	/* 从 RestrictInfo 列表中提取任何伪常量 quals */
	return extract_actual_clauses(fc_quals, true);
}

/*
 * create_gating_plan
 *	  处理伪常量 qual 子句
 *
 * 在已构建的计划顶部添加一个 gating Result 节点。
 */
static Plan * fc_create_gating_plan(PlannerInfo *fc_root, Path *fc_path, Plan *fc_plan,
				   List *fc_gating_quals)
{
	Plan	   *fc_gplan;
	Plan	   *fc_splan;

	Assert(fc_gating_quals);

	/*
	 * 我们可能已经有一个微不足道的 Result 计划。如果在另一个上叠加一个 Result 是毫无意义的，
	 * 所以如果适用，只需丢弃输入计划。
	 * （我们假设它的 targetlist 不感兴趣；它应与 build_path_tlist 的结果相同，或是一个简化版本。）
	 */
	fc_splan = fc_plan;
	if (IsA(fc_plan, Result))
	{
		Result	   *fc_rplan = (Result *) fc_plan;

		if (fc_rplan->plan.lefttree == NULL &&
			fc_rplan->resconstantqual == NULL)
			fc_splan = NULL;
	}

	/*
	 * 由于我们无论如何都需要一个 Result 节点，因此始终返回路径请求的 tlist；
	 * 即使父节点没有请求 CP_EXACT_TLIST，也永远不是错误的选择。
	 */
	fc_gplan = (Plan *) fc_make_result(fc_build_path_tlist(fc_root, fc_path),
								 (Node *) fc_gating_quals,
								 fc_splan);

	/*
	 * 请注意，在进行 gating 时，我们不会改变成本或大小估计。
	 * qual 评估的成本已经包含在子计划成本中。
	 * 保持大小不变相当于假设 gating qual 会成功，这对于规划上层查询是保守的估计。
	 * 我们当然不想假设输出大小为零（除非 gating qual 实际为常量 FALSE，而该情况在 clausesel.c 中处理）。
	 * 在两种情况下插值是愚蠢的，因为它无法反映运行时的真实情况，而且在大多数情况下，
	 * 我们对 gating qual 为真的概率只有非常不好的想法。
	 */
	fc_copy_plan_costsize(fc_gplan, fc_plan);

	/* Gating quals 可能不安全，因此最好使用 Path 的安全标志 */
	fc_gplan->parallel_safe = fc_path->parallel_safe;

	return fc_gplan;
}

/*
 * create_join_plan
 *	  为 'best_path' 创建一个连接计划，并（递归地）为其内外路径创建计划。
 */
static Plan * fc_create_join_plan(PlannerInfo *fc_root, JoinPath *fc_best_path)
{
	Plan	   *fc_plan;
	List	   *fc_gating_clauses;

	switch (fc_best_path->path.pathtype)
	{
		case T_MergeJoin:
			fc_plan = (Plan *) fc_create_mergejoin_plan(fc_root,
												  (MergePath *) fc_best_path);
			break;
		case T_HashJoin:
			fc_plan = (Plan *) fc_create_hashjoin_plan(fc_root,
												 (HashPath *) fc_best_path);
			break;
		case T_NestLoop:
			fc_plan = (Plan *) fc_create_nestloop_plan(fc_root,
												 (NestPath *) fc_best_path);
			break;
		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) fc_best_path->path.pathtype);
			fc_plan = NULL;		/* 保持编译器安静 */
			break;
	}

	/*
	 * 如果此节点附加有任何伪常量子句，请插入一个评估伪常量作为一次性
	 * 资格的门控结果节点。
	 */
	fc_gating_clauses = get_gating_quals(fc_root, fc_best_path->joinrestrictinfo);
	if (fc_gating_clauses)
		fc_plan = fc_create_gating_plan(fc_root, (Path *) fc_best_path, fc_plan,
								  fc_gating_clauses);

#ifdef NOT_USED

	/*
	 * * 昂贵的函数提升可能已将局部谓词 * 拉入此路径节点。将它们放入计划节点的 qpqual 中。 * JMH,
	 * 1992年6月15日
	 */

#ifndef FDD
    // 当前版本中，已经没有get_loc_restrictinfo函数，所以注释
    if (get_loc_restrictinfo(fc_best_path) != NIL)
            set_qpqual((Plan) fc_plan,
                       list_concat(get_qpqual((Plan) fc_plan),
                                   get_actual_clauses(get_loc_restrictinfo(fc_best_path))));
#endif //end FDD
#endif //end NOT_USED

	return fc_plan;
}

/*
 * mark_async_capable_plan
 *		检查从 Path 节点创建的 Plan 节点是否是异步可用的，
 *		如果是，则将 Plan 节点标记为是，并返回 true，否则返回 false。
 */
static bool fc_mark_async_capable_plan(Plan *fc_plan, Path *fc_path)
{
	switch (nodeTag(fc_path))
	{
		case T_SubqueryScanPath:
			{
				SubqueryScan *fc_scan_plan = (SubqueryScan *) fc_plan;

				/*
				 * 如果生成的计划节点包括一个 gating Result 节点，
				 * 我们无法异步执行它。
				 */
				if (IsA(fc_plan, Result))
					return false;

				/*
				 * 如果位于异步可用计划节点顶部的 SubqueryScan 节点是可删除的， 
				 * 则将其视为异步可用。
				 */
				if (trivial_subqueryscan(fc_scan_plan) &&
					fc_mark_async_capable_plan(fc_scan_plan->subplan,
											((SubqueryScanPath *) fc_path)->subpath))
					break;
				return false;
			}
		case T_ForeignPath:
			{
				FdwRoutine *fc_fdwroutine = fc_path->parent->fdwroutine;

				/*
				 * 如果生成的计划节点包括一个 gating Result 节点，
				 * 我们无法异步执行它。
				 */
				if (IsA(fc_plan, Result))
					return false;

				Assert(fc_fdwroutine != NULL);
				if (fc_fdwroutine->IsForeignPathAsyncCapable != NULL &&
					fc_fdwroutine->IsForeignPathAsyncCapable((ForeignPath *) fc_path))
					break;
				return false;
			}
		case T_ProjectionPath:

			/*
			 * 如果生成的计划节点为投影包含一个 Result 节点，
			 * 我们无法异步执行它。
			 */
			if (IsA(fc_plan, Result))
				return false;

			/*
			 * create_projection_plan() 将提取子计划，
			 * 因此通过子路径检查能力。
			 */
			if (fc_mark_async_capable_plan(fc_plan,
										((ProjectionPath *) fc_path)->subpath))
				return true;
			return false;
		default:
			return false;
	}

	fc_plan->async_capable = true;

	return true;
}

/*
 * create_append_plan
 *	  为 'best_path' 创建一个 Append 计划，并（递归地）为其子路径创建计划。
 *
 *	  返回一个 Plan 节点。
 */
static Plan * fc_create_append_plan(PlannerInfo *fc_root, AppendPath *fc_best_path, int fc_flags)
{
	Append	   *fc_plan;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);
	int			fc_orig_tlist_length = list_length(fc_tlist);
	bool		fc_tlist_was_changed = false;
	List	   *fc_pathkeys = fc_best_path->path.pathkeys;
	List	   *fc_subplans = NIL;
	ListCell   *fc_subpaths;
	int			fc_nasyncplans = 0;
	RelOptInfo *fc_rel = fc_best_path->path.parent;
	PartitionPruneInfo *fc_partpruneinfo = NULL;
	int			fc_nodenumsortkeys = 0;
	AttrNumber *fc_nodeSortColIdx = NULL;
	Oid		   *fc_nodeSortOperators = NULL;
	Oid		   *fc_nodeCollations = NULL;
	bool	   *fc_nodeNullsFirst = NULL;
	bool		fc_consider_async = false;

	/*
	 * 子路径列表可能是空的，如果每个子项通过约束排除被证明为空。在这种情况下生成一个返回
	 * 不带行的虚拟计划。
	 *
	 * 请注意，在某些情况下，即使根本没有附加构造，但我们知道关系是空的，也会生成没有成员的 AppendPath 
	 * （见 set_dummy_rel_pathlist 和 mark_dummy_rel）。
	 */
	if (fc_best_path->subpaths == NIL)
	{
		/* 生成一个具有常量-FALSE gating qual 的 Result 计划 */
		Plan	   *fc_plan;

		fc_plan = (Plan *) fc_make_result(fc_tlist,
									(Node *) list_make1(makeBoolConst(false,
																	  false)),
									NULL);

		fc_copy_generic_path_info(fc_plan, (Path *) fc_best_path);

		return fc_plan;
	}

	/*
	 * 否则构建一个 Append 计划。请注意，如果只有一个子项，
	 * Append 就毫无用处；但是我们等到 setrefs.c 之后再处理它。
	 * 在这里这样做不起作用，因为子扫描计划的 varno 将与请求输出的父关系 Vars 不匹配。
	 *
	 * 我们没有将实际创建 Append 节点拆分为单独的 make_xxx 函数。这是因为我们希望在对个别子计划进行
	 * 处理之前，先运行 prepare_sort_from_pathkeys，这样可以更轻松地交叉检查排序信息。
	 */
	fc_plan = makeNode(Append);
	fc_plan->plan.targetlist = fc_tlist;
	fc_plan->plan.qual = NIL;
	fc_plan->plan.lefttree = NULL;
	fc_plan->plan.righttree = NULL;
	fc_plan->apprelids = fc_rel->relids;

	if (fc_pathkeys != NIL)
	{
		
/*
		 * 计算排序列信息，并根据需要调整 Append 的 tlist。
		 * 因为我们传递了 adjust_tlist_in_place = true，所以我们可以忽略
		 * 函数结果；它必须是同一计划节点。然而，我们需要
		 * 检测是否添加了任何 tlist 条目。
		 */
		(void) fc_prepare_sort_from_pathkeys((Plan *) fc_plan, fc_pathkeys,
										  fc_best_path->path.parent->relids,
										  NULL,
										  true,
										  &fc_nodenumsortkeys,
										  &fc_nodeSortColIdx,
										  &fc_nodeSortOperators,
										  &fc_nodeCollations,
										  &fc_nodeNullsFirst);
		fc_tlist_was_changed = (fc_orig_tlist_length != list_length(fc_plan->plan.targetlist));
	}

	/* 如果合适，考虑异步追加 */
	fc_consider_async = (enable_async_append && fc_pathkeys == NIL &&
					  !fc_best_path->path.parallel_safe &&
					  list_length(fc_best_path->subpaths) > 1);

	/* 为每个子节点构建计划 */
	foreach(fc_subpaths, fc_best_path->subpaths)
	{
		Path	   *fc_subpath = (Path *) lfirst(fc_subpaths);
		Plan	   *fc_subplan;

		/* 必须坚持所有子节点返回相同的 tlist */
		fc_subplan = fc_create_plan_recurse(fc_root, fc_subpath, CP_EXACT_TLIST);

		/*
		 * 对于有序的 Append，如果子计划的顺序不够，
		 * 我们必须插入一个 Sort 节点。
		 */
		if (fc_pathkeys != NIL)
		{
			int			fc_numsortkeys;
			AttrNumber *fc_sortColIdx;
			Oid		   *fc_sortOperators;
			Oid		   *fc_collations;
			bool	   *fc_nullsFirst;

			/*
			 * 计算排序列信息，并根据需要调整子计划的 tlist。
			 * 我们必须对即使不需要显式排序的子计划应用 prepare_sort_from_pathkeys，
			 * 以确保它们返回 Append 所期望的相同排序关键列。
			 */
			fc_subplan = fc_prepare_sort_from_pathkeys(fc_subplan, fc_pathkeys,
												 fc_subpath->parent->relids,
												 fc_nodeSortColIdx,
												 false,
												 &fc_numsortkeys,
												 &fc_sortColIdx,
												 &fc_sortOperators,
												 &fc_collations,
												 &fc_nullsFirst);

			/*
			 * 检查我们是否得到了相同的排序关键字信息。我们只是
			 * 断言排序操作符匹配，因为这些仅依赖于
			 * pathkeys；但检查排序列编号是个好主意，以确保 tlist 匹配。
			 */
			Assert(fc_numsortkeys == fc_nodenumsortkeys);
			if (memcmp(fc_sortColIdx, fc_nodeSortColIdx,
					   fc_numsortkeys * sizeof(AttrNumber)) != 0)
				elog(ERROR, "Append child's targetlist doesn't match Append");
			Assert(memcmp(fc_sortOperators, fc_nodeSortOperators,
						  fc_numsortkeys * sizeof(Oid)) == 0);
			Assert(memcmp(fc_collations, fc_nodeCollations,
						  fc_numsortkeys * sizeof(Oid)) == 0);
			Assert(memcmp(fc_nullsFirst, fc_nodeNullsFirst,
						  fc_numsortkeys * sizeof(bool)) == 0);

			/* 现在，如果子计划的顺序不足，则插入一个排序节点 */
			if (!pathkeys_contained_in(fc_pathkeys, fc_subpath->pathkeys))
			{
				Sort	   *fc_sort = fc_make_sort(fc_subplan, fc_numsortkeys,
											 fc_sortColIdx, fc_sortOperators,
											 fc_collations, fc_nullsFirst);

				fc_label_sort_with_costsize(fc_root, fc_sort, fc_best_path->limit_tuples);
				fc_subplan = (Plan *) fc_sort;
			}
		}

		/* 如果需要，检查子计划是否可以异步执行 */
		if (fc_consider_async && fc_mark_async_capable_plan(fc_subplan, fc_subpath))
		{
			Assert(fc_subplan->async_capable);
			++fc_nasyncplans;
		}

		fc_subplans = lappend(fc_subplans, fc_subplan);
	}

	/* 
	 * 如果存在任何 quals，它们可能在执行期间用于进一步的分区修剪。
	 * 收集执行器所需的信息以进行分区修剪。
	 */
	if (enable_partition_pruning)
	{
		List	   *fc_prunequal;

		fc_prunequal = extract_actual_clauses(fc_rel->baserestrictinfo, false);

		if (fc_best_path->path.param_info)
		{
			List	   *fc_prmquals = fc_best_path->path.param_info->ppi_clauses;

			fc_prmquals = extract_actual_clauses(fc_prmquals, false);
			fc_prmquals = (List *) fc_replace_nestloop_params(fc_root,
														(Node *) fc_prmquals);

			fc_prunequal = list_concat(fc_prunequal, fc_prmquals);
		}

		if (fc_prunequal != NIL)
			fc_partpruneinfo =
				make_partition_pruneinfo(fc_root, fc_rel,
										 fc_best_path->subpaths,
										 fc_prunequal);
	}

	fc_plan->appendplans = fc_subplans;
	fc_plan->nasyncplans = fc_nasyncplans;
	fc_plan->first_partial_plan = fc_best_path->first_partial_path;
	fc_plan->part_prune_info = fc_partpruneinfo;

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	/* 
	 * 如果 prepare_sort_from_pathkeys 添加了排序列，但是我们被告知
	 * 生成确切的 tlist 或狭窄的 tlist，我们应该再次去掉
	 * 排序列。我们必须注入一个投影节点来做到这一点。
	 */
	if (fc_tlist_was_changed && (fc_flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
	{
		fc_tlist = list_truncate(list_copy(fc_plan->plan.targetlist),
							  fc_orig_tlist_length);
		return fc_inject_projection_plan((Plan *) fc_plan, fc_tlist,
									  fc_plan->plan.parallel_safe);
	}
	else
		return (Plan *) fc_plan;
}

/* 
 * create_merge_append_plan
 *	  为 'best_path' 和（递归）其子路径创建一个 MergeAppend 计划。
 *
 *	  返回一个计划节点。
 */
static Plan * fc_create_merge_append_plan(PlannerInfo *fc_root, MergeAppendPath *fc_best_path,
						 int fc_flags)
{
	MergeAppend *fc_node = makeNode(MergeAppend);
	Plan	   *fc_plan = &fc_node->plan;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);
	int			fc_orig_tlist_length = list_length(fc_tlist);
	bool		fc_tlist_was_changed;
	List	   *fc_pathkeys = fc_best_path->path.pathkeys;
	List	   *fc_subplans = NIL;
	ListCell   *fc_subpaths;
	RelOptInfo *fc_rel = fc_best_path->path.parent;
	PartitionPruneInfo *fc_partpruneinfo = NULL;

	/* 
	 * 我们没有将 MergeAppend 节点的实际创建分离到
	 * 一个单独的 make_xxx 函数中。这是因为我们希望在
	 * 对个别子计划进行 prepare_sort_from_pathkeys 之前先运行它，
	 * 以简化排序信息的交叉检查。
	 */
	fc_copy_generic_path_info(fc_plan, (Path *) fc_best_path);
	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->apprelids = fc_rel->relids;

	/* 
	 * 计算排序列信息，并根据需要调整 MergeAppend 的 tlist。
	 * 因为我们传递 adjust_tlist_in_place = true，所以我们可以忽略
	 * 函数结果；它必须是相同的计划节点。但是，我们接下来需要
	 * 检测是否添加了任何 tlist 条目。
	 */
	(void) fc_prepare_sort_from_pathkeys(fc_plan, fc_pathkeys,
									  fc_best_path->path.parent->relids,
									  NULL,
									  true,
									  &fc_node->numCols,
									  &fc_node->sortColIdx,
									  &fc_node->sortOperators,
									  &fc_node->collations,
									  &fc_node->nullsFirst);
	fc_tlist_was_changed = (fc_orig_tlist_length != list_length(fc_plan->targetlist));

	/* 
	 * 现在准备子计划。我们必须对即使不需要显式排序的子计划
	 * 应用 prepare_sort_from_pathkeys，以确保它们返回的排序键
	 * 列与 MergeAppend 期待的相同。
	 */
	foreach(fc_subpaths, fc_best_path->subpaths)
	{
		Path	   *fc_subpath = (Path *) lfirst(fc_subpaths);
		Plan	   *fc_subplan;
		int			fc_numsortkeys;
		AttrNumber *fc_sortColIdx;
		Oid		   *fc_sortOperators;
		Oid		   *fc_collations;
		bool	   *fc_nullsFirst;

		/* 构建子计划 */
		/* 必须坚持所有子节点返回相同的 tlist */
		fc_subplan = fc_create_plan_recurse(fc_root, fc_subpath, CP_EXACT_TLIST);

		/* 计算排序列信息，并根据需要调整子计划的 tlist */
		fc_subplan = fc_prepare_sort_from_pathkeys(fc_subplan, fc_pathkeys,
											 fc_subpath->parent->relids,
											 fc_node->sortColIdx,
											 false,
											 &fc_numsortkeys,
											 &fc_sortColIdx,
											 &fc_sortOperators,
											 &fc_collations,
											 &fc_nullsFirst);

		/* 
		 * 检查我们是否得到了相同的排序键信息。我们只需断言
		 * 排序操作符匹配，因为这些仅依赖于路径键；
		 * 但是，明确检查排序列的数量似乎是个好主意，
		 * 以确保 tlists 确实匹配。
		 */
		Assert(fc_numsortkeys == fc_node->numCols);
		if (memcmp(fc_sortColIdx, fc_node->sortColIdx,
				   fc_numsortkeys * sizeof(AttrNumber)) != 0)
			elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend");
		Assert(memcmp(fc_sortOperators, fc_node->sortOperators,
					  fc_numsortkeys * sizeof(Oid)) == 0);
		Assert(memcmp(fc_collations, fc_node->collations,
					  fc_numsortkeys * sizeof(Oid)) == 0);
		Assert(memcmp(fc_nullsFirst, fc_node->nullsFirst,
					  fc_numsortkeys * sizeof(bool)) == 0);

		/* 现在，如果子计划的顺序不足，则插入一个排序节点 */
		if (!pathkeys_contained_in(fc_pathkeys, fc_subpath->pathkeys))
		{
			Sort	   *fc_sort = fc_make_sort(fc_subplan, fc_numsortkeys,
										 fc_sortColIdx, fc_sortOperators,
										 fc_collations, fc_nullsFirst);

			fc_label_sort_with_costsize(fc_root, fc_sort, fc_best_path->limit_tuples);
			fc_subplan = (Plan *) fc_sort;
		}

		fc_subplans = lappend(fc_subplans, fc_subplan);
	}

	/* 
	 * 如果存在任何 quals，它们可能在执行期间用于进一步的分区修剪。
	 * 收集执行器所需的信息以进行分区修剪。
	 */
	if (enable_partition_pruning)
	{
		List	   *fc_prunequal;

		fc_prunequal = extract_actual_clauses(fc_rel->baserestrictinfo, false);

		if (fc_best_path->path.param_info)
		{
			List	   *fc_prmquals = fc_best_path->path.param_info->ppi_clauses;

			fc_prmquals = extract_actual_clauses(fc_prmquals, false);
			fc_prmquals = (List *) fc_replace_nestloop_params(fc_root,
														(Node *) fc_prmquals);

			fc_prunequal = list_concat(fc_prunequal, fc_prmquals);
		}

		if (fc_prunequal != NIL)
			fc_partpruneinfo = make_partition_pruneinfo(fc_root, fc_rel,
													 fc_best_path->subpaths,
													 fc_prunequal);
	}

	fc_node->mergeplans = fc_subplans;
	fc_node->part_prune_info = fc_partpruneinfo;

	/* 
	 * 如果 prepare_sort_from_pathkeys 添加了排序列，但是我们被告知
	 * 生成确切的 tlist 或狭窄的 tlist，我们应该再次去掉
	 * 排序列。我们必须注入一个投影节点来做到这一点。
	 */
	if (fc_tlist_was_changed && (fc_flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
	{
		fc_tlist = list_truncate(list_copy(fc_plan->targetlist), fc_orig_tlist_length);
		return fc_inject_projection_plan(fc_plan, fc_tlist, fc_plan->parallel_safe);
	}
	else
		return fc_plan;
}

/* 
 * create_group_result_plan
 *	  为 'best_path' 创建一个结果计划。
 *	  这仅用于退化分组情况。
 *
 *	  返回一个计划节点。
 */
static Result * fc_create_group_result_plan(PlannerInfo *fc_root, GroupResultPath *fc_best_path)
{
	Result	   *fc_plan;
	List	   *fc_tlist;
	List	   *fc_quals;

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	/* best_path->quals 只是裸条款 */
	fc_quals = fc_order_qual_clauses(fc_root, fc_best_path->quals);

	fc_plan = fc_make_result(fc_tlist, (Node *) fc_quals, NULL);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/* 
 * create_project_set_plan
 *	  为 'best_path' 创建一个 ProjectSet 计划。
 *
 *	  返回一个计划节点。
 */
static ProjectSet * fc_create_project_set_plan(PlannerInfo *fc_root, ProjectSetPath *fc_best_path)
{
	ProjectSet *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_tlist;

	/* 由于我们打算投影，因此不需要限制子 tlist */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, 0);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	fc_plan = fc_make_project_set(fc_tlist, fc_subplan);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/* 
 * create_material_plan
 *	  为 'best_path' 创建一个 Material 计划，并（递归）生成其
 *	  子路径的计划。
 *
 *	  返回一个计划节点。
 */
static Material * fc_create_material_plan(PlannerInfo *fc_root, MaterialPath *fc_best_path, int fc_flags)
{
	Material   *fc_plan;
	Plan	   *fc_subplan;

	/* 
	 * 我们不希望在物化的元组中有任何多余的列，因此请求
	 * 一个较小的 tlist。否则，由于 Material 不进行投影，tlist
	 * 要求会通过。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  fc_flags | CP_SMALL_TLIST);

	fc_plan = fc_make_material(fc_subplan);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/* 
 * create_memoize_plan
 *	  为 'best_path' 创建一个 Memoize 计划，并（递归）生成
 *	  其子路径的计划。
 *
 *	  返回一个计划节点。
 */
static Memoize * fc_create_memoize_plan(PlannerInfo *fc_root, MemoizePath *fc_best_path, int fc_flags)
{
	Memoize    *fc_plan;
	Bitmapset  *fc_keyparamids;
	Plan	   *fc_subplan;
	Oid		   *fc_operators;
	Oid		   *fc_collations;
	List	   *fc_param_exprs = NIL;
	ListCell   *fc_lc;
	ListCell   *fc_lc2;
	int			fc_nkeys;
	int			fc_i;

	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  fc_flags | CP_SMALL_TLIST);

	fc_param_exprs = (List *) fc_replace_nestloop_params(fc_root, (Node *)
												   fc_best_path->param_exprs);

	fc_nkeys = list_length(fc_param_exprs);
	Assert(fc_nkeys > 0);
	fc_operators = palloc(fc_nkeys * sizeof(Oid));
	fc_collations = palloc(fc_nkeys * sizeof(Oid));

	fc_i = 0;
	forboth(fc_lc, fc_param_exprs, fc_lc2, fc_best_path->hash_operators)
	{
		Expr	   *fc_param_expr = (Expr *) lfirst(fc_lc);
		Oid			fc_opno = lfirst_oid(fc_lc2);

		fc_operators[fc_i] = fc_opno;
		fc_collations[fc_i] = exprCollation((Node *) fc_param_expr);
		fc_i++;
	}

	fc_keyparamids = pull_paramids((Expr *) fc_param_exprs);

	fc_plan = fc_make_memoize(fc_subplan, fc_operators, fc_collations, fc_param_exprs,
						fc_best_path->singlerow, fc_best_path->binary_mode,
						fc_best_path->est_entries, fc_keyparamids);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/* 
 * create_unique_plan
 *	  为 'best_path' 创建一个 Unique 计划，并（递归）生成
 *	  其子路径的计划。
 *
 *	  返回一个计划节点。
 */
static Plan * fc_create_unique_plan(PlannerInfo *fc_root, UniquePath *fc_best_path, int fc_flags)
{
	Plan	   *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_in_operators;
	List	   *fc_uniq_exprs;
	List	   *fc_newtlist;
	int			fc_nextresno;
	bool		fc_newitems;
	int			fc_numGroupCols;
	AttrNumber *fc_groupColIdx;
	Oid		   *fc_groupCollations;
	int			fc_groupColPos;
	ListCell   *fc_l;

	/* Unique 不进行投影，因此 tlist 要求会通过 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, fc_flags);

	/* 如果我们不需要进行任何实际的唯一化，则完成 */
	if (fc_best_path->umethod == UNIQUE_PATH_NOOP)
		return fc_subplan;

	/* 
	 * 按照构造，子计划具有一个“扁平”的 tlist，仅包含这里和上层
	 * 所需的变量。我们应该进行唯一化的值可能是这些变量的表达式。
	 * 我们必须将任何这样的表达式添加到子计划的 tlist。
	 * 
	 * 如果子计划是一个简单的扫描计划，它可能具有“物理”tlist。
	 * 如果我们要排序，则应将其减少为常规 tlist，以避免排序
	 * 超过我们需要的数据。对于哈希，如果我们不需要添加任何表达式，
	 * tlist 应保持不变；但是如果我们确实需要添加表达式，那么在
	 * 运行时仍然需要一个投影步骤，因此我们不妨删除不需要的项目。
	 * 因此 newtlist 从 build_path_tlist() 开始，而不仅仅是子计划的
	 * tlist 的副本；并且除非我们正在排序或需要添加内容，否则不会
	 * 将其安装到子计划中。
	 */
	fc_in_operators = fc_best_path->in_operators;
	fc_uniq_exprs = fc_best_path->uniq_exprs;

	/* 将修改后的子计划 tlist 初始化为仅包含“所需”的变量 */
	fc_newtlist = fc_build_path_tlist(fc_root, &fc_best_path->path);
	fc_nextresno = list_length(fc_newtlist) + 1;
	fc_newitems = false;

	foreach(fc_l, fc_uniq_exprs)
	{
		Expr	   *fc_uniqexpr = lfirst(fc_l);
		TargetEntry *fc_tle;

		fc_tle = tlist_member(fc_uniqexpr, fc_newtlist);
		if (!fc_tle)
		{
			fc_tle = makeTargetEntry((Expr *) fc_uniqexpr,
								  fc_nextresno,
								  NULL,
								  false);
			fc_newtlist = lappend(fc_newtlist, fc_tle);
			fc_nextresno++;
			fc_newitems = true;
		}
	}

	/* 使用 change_plan_targetlist 以防我们需要插入一个结果节点 */
	if (fc_newitems || fc_best_path->umethod == UNIQUE_PATH_SORT)
		fc_subplan = change_plan_targetlist(fc_subplan, fc_newtlist,
										 fc_best_path->path.parallel_safe);

	/* 
	 * 构建控制信息，表明哪些子计划输出列将
	 * 被分组步骤检查。不幸的是，我们无法将这与
	 * 先前的循环合并，因为那时我们不知道最终使用
	 * 的子计划 tlist 的哪个版本。
	 */
	fc_newtlist = fc_subplan->targetlist;
	fc_numGroupCols = list_length(fc_uniq_exprs);
	fc_groupColIdx = (AttrNumber *) palloc(fc_numGroupCols * sizeof(AttrNumber));
	fc_groupCollations = (Oid *) palloc(fc_numGroupCols * sizeof(Oid));

	fc_groupColPos = 0;
	foreach(fc_l, fc_uniq_exprs)
	{
		Expr	   *fc_uniqexpr = lfirst(fc_l);
		TargetEntry *fc_tle;

		fc_tle = tlist_member(fc_uniqexpr, fc_newtlist);
		if (!fc_tle)				/* 不应该发生 */
			elog(ERROR, "failed to find unique expression in subplan tlist");
		fc_groupColIdx[fc_groupColPos] = fc_tle->resno;
		fc_groupCollations[fc_groupColPos] = exprCollation((Node *) fc_tle->expr);
		fc_groupColPos++;
	}

	if (fc_best_path->umethod == UNIQUE_PATH_HASH)
	{
		Oid		   *fc_groupOperators;

		/* 
		 * 获取 Agg 节点要使用的可哈希的相等操作符。
		 * 通常这些与 IN 子句操作符相同，但如果
		 * 这些是跨类型操作符，则相等操作符是
		 * IN 子句操作符 RHS 数据类型的操作符。
		 */
		fc_groupOperators = (Oid *) palloc(fc_numGroupCols * sizeof(Oid));
		fc_groupColPos = 0;
		foreach(fc_l, fc_in_operators)
		{
			Oid			fc_in_oper = lfirst_oid(fc_l);
			Oid			fc_eq_oper;

			if (!get_compatible_hash_operators(fc_in_oper, NULL, &fc_eq_oper))
				elog(ERROR, "could not find compatible hash operator for operator %u",
					 fc_in_oper);
			fc_groupOperators[fc_groupColPos++] = fc_eq_oper;
		}

		
/*
		 * 由于Agg节点将进行投影，我们可以给它最小的输出tlist，而不包含我们可能已添加到子计划tlist中的任何内容。
		 */
		fc_plan = (Plan *) make_agg(fc_build_path_tlist(fc_root, &fc_best_path->path),
								 NIL,
								 AGG_HASHED,
								 AGGSPLIT_SIMPLE,
								 fc_numGroupCols,
								 fc_groupColIdx,
								 fc_groupOperators,
								 fc_groupCollations,
								 NIL,
								 NIL,
								 fc_best_path->path.rows,
								 0,
								 fc_subplan);
	}
	else
	{
		List	   *fc_sortList = NIL;
		Sort	   *fc_sort;

		/* 创建一个ORDER BY列表以兼容地排序输入 */
		fc_groupColPos = 0;
		foreach(fc_l, fc_in_operators)
		{
			Oid			fc_in_oper = lfirst_oid(fc_l);
			Oid			fc_sortop;
			Oid			fc_eqop;
			TargetEntry *fc_tle;
			SortGroupClause *fc_sortcl;

			fc_sortop = get_ordering_op_for_equality_op(fc_in_oper, false);
			if (!OidIsValid(fc_sortop))	/* 不应该发生 */
				elog(ERROR, "could not find ordering operator for equality operator %u",
					 fc_in_oper);

			/*
			 * Unique节点需要相等运算符。通常这些与IN子句运算符相同，但如果它们是跨类型运算符，则相等运算符是IN子句运算符右侧数据类型的运算符。
			 */
			fc_eqop = get_equality_op_for_ordering_op(fc_sortop, NULL);
			if (!OidIsValid(fc_eqop))	/* 不应该发生 */
				elog(ERROR, "could not find equality operator for ordering operator %u",
					 fc_sortop);

			fc_tle = get_tle_by_resno(fc_subplan->targetlist,
								   fc_groupColIdx[fc_groupColPos]);
			Assert(fc_tle != NULL);

			fc_sortcl = makeNode(SortGroupClause);
			fc_sortcl->tleSortGroupRef = assignSortGroupRef(fc_tle,
														 fc_subplan->targetlist);
			fc_sortcl->eqop = fc_eqop;
			fc_sortcl->sortop = fc_sortop;
			fc_sortcl->nulls_first = false;
			fc_sortcl->hashable = false;	/* 不需要使其准确 */
			fc_sortList = lappend(fc_sortList, fc_sortcl);
			fc_groupColPos++;
		}
		fc_sort = make_sort_from_sortclauses(fc_sortList, fc_subplan);
		fc_label_sort_with_costsize(fc_root, fc_sort, -1.0);
		fc_plan = (Plan *) fc_make_unique_from_sortclauses((Plan *) fc_sort, fc_sortList);
	}

	/* 从 Path 复制成本数据到 Plan */
	fc_copy_generic_path_info(fc_plan, &fc_best_path->path);

	return fc_plan;
}

/*
 * create_gather_plan
 *
 *	  为 'best_path' 创建一个 Gather 计划，并为其子路径
 *	  递归地创建计划。
 */
static Gather * fc_create_gather_plan(PlannerInfo *fc_root, GatherPath *fc_best_path)
{
	Gather	   *fc_gather_plan;
	Plan	   *fc_subplan;
	List	   *fc_tlist;

	/*
	 * 将投影下推到子节点。这样，投影工作
	 * 就能并行化，并且结果中不会有系统列（它们
	 * 无法通过元组队列传输，因为它使用 MinimalTuple
	 * 表示）。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, CP_EXACT_TLIST);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	fc_gather_plan = fc_make_gather(fc_tlist,
							  NIL,
							  fc_best_path->num_workers,
							  assign_special_exec_param(fc_root),
							  fc_best_path->single_copy,
							  fc_subplan);

	fc_copy_generic_path_info(&fc_gather_plan->plan, &fc_best_path->path);

	/* 对于并行计划使用并行模式。 */
	fc_root->glob->parallelModeNeeded = true;

	return fc_gather_plan;
}

/*
 * create_gather_merge_plan
 *
 *	  为 'best_path' 创建一个 Gather Merge 计划，并为其子路径
 *	  递归地创建计划。
 */
static GatherMerge * fc_create_gather_merge_plan(PlannerInfo *fc_root, GatherMergePath *fc_best_path)
{
	GatherMerge *fc_gm_plan;
	Plan	   *fc_subplan;
	List	   *fc_pathkeys = fc_best_path->path.pathkeys;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	/* 与 Gather 一样，在工作节点中投影掉列。 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, CP_EXACT_TLIST);

	/* 为 GatherMerge 计划创建一个外壳。 */
	fc_gm_plan = makeNode(GatherMerge);
	fc_gm_plan->plan.targetlist = fc_tlist;
	fc_gm_plan->num_workers = fc_best_path->num_workers;
	fc_copy_generic_path_info(&fc_gm_plan->plan, &fc_best_path->path);

	/* 分配重扫描参数。 */
	fc_gm_plan->rescan_param = assign_special_exec_param(fc_root);

	/* 没有 pathkeys 时 Gather Merge 是没有意义的；使用 Gather 代替。 */
	Assert(fc_pathkeys != NIL);

	/* 计算排序列信息，并根据需要调整子计划的 tlist */
	fc_subplan = fc_prepare_sort_from_pathkeys(fc_subplan, fc_pathkeys,
										 fc_best_path->subpath->parent->relids,
										 fc_gm_plan->sortColIdx,
										 false,
										 &fc_gm_plan->numCols,
										 &fc_gm_plan->sortColIdx,
										 &fc_gm_plan->sortOperators,
										 &fc_gm_plan->collations,
										 &fc_gm_plan->nullsFirst);


	/*
	 * 所有 Gather Merge 路径应该已经通过添加显式
	 * 排序节点或使用预排序输入来确保必要的
	 * 排序顺序。我们不能简单地在附加的 pathkeys 上添加排序，
	 * 因为我们无法保证该排序是安全的。例如，表达式可能
	 * 是不稳定的或其他并行不安全的。
	 */
	if (!pathkeys_contained_in(fc_pathkeys, fc_best_path->subpath->pathkeys))
		elog(ERROR, "gather merge input not sufficiently sorted");

	/* 现在在 GatherMerge 下插入子计划。 */
	fc_gm_plan->plan.lefttree = fc_subplan;

	/* 对于并行计划使用并行模式。 */
	fc_root->glob->parallelModeNeeded = true;

	return fc_gm_plan;
}

/*
 * create_projection_plan
 *
 *	  创建一个计划树来执行投影步骤，并为其子路径
 *	  递归地创建计划。我们可能需要一个 Result 节点来进行投影，
 *	  但有时我们可以让子计划完成这项工作。
 */
static Plan * fc_create_projection_plan(PlannerInfo *fc_root, ProjectionPath *fc_best_path, int fc_flags)
{
	Plan	   *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_tlist;
	bool		fc_needs_result_node = false;

	/*
	 * 将我们的子路径转换为计划，并确定是否需要一个 Result
	 * 节点。
	 *
	 * 在大多数情况下，如果我们不需要投影，create_projection_path
	 * 会设置 dummypp，但并不总是如此。首先，一些 createplan.c
	 * 例程改变它们节点的 tlists。（一个例子是
	 * create_merge_append_plan 可能会向
	 * MergeAppend 添加 resjunk 排序列。）其次，create_projection_path
	 * 无法知道哪个路径节点将放置在投影路径的上方，因此
	 * 无法预测它是否需要确切的 tlist。由于这两个原因，
	 * 我们必须在这里重新检查。
	 */
	if (fc_use_physical_tlist(fc_root, &fc_best_path->path, fc_flags))
	{
		/*
		 * 我们的调用者并不真的关心我们返回什么 tlist，
		 * 所以我们实际上不需要投影。然而，我们仍然可能需要
		 * 确保适当的 sortgroupref 标签，如果调用者关心这些的话。
		 */
		fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, 0);
		fc_tlist = fc_subplan->targetlist;
		if (fc_flags & CP_LABEL_TLIST)
			apply_pathtarget_labeling_to_tlist(fc_tlist,
											   fc_best_path->path.pathtarget);
	}
	else if (is_projection_capable_path(fc_best_path->subpath))
	{
		/*
		 * 我们的调用者要求我们返回确切的 tlist，但不需要
		 * 单独的结果节点，因为子路径是可以进行投影的。
		 * 告诉 create_plan_recurse 我们将忽略它生成的 tlist。
		 */
		fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
									  CP_IGNORE_TLIST);
		Assert(is_projection_capable_plan(fc_subplan));
		fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);
	}
	else
	{
		/*
		 * 看起来我们需要一个结果节点，除非碰巧请求的
		 * tlist 正好是子节点想要生成的。
		 */
		fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, 0);
		fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);
		fc_needs_result_node = !tlist_same_exprs(fc_tlist, fc_subplan->targetlist);
	}

	/*
	 * 如果我们在是否包含 Result 节点的决定上做出了不同的
	 * 判断，而不是 create_projection_path，那么我们的成本估算
	 * 会稍微错误；但应标记计划为我们实际使用的成本估算，
	 * 而不是“修正”的成本估算。（XXX 如果我们将更多
	 * 的 sortcolumn 设置逻辑移入 Path 创建中，这可以得到
	 * 清理，但这会增加创建可能最终不使用的 Paths 的费用。）
	 */
	if (!fc_needs_result_node)
	{
		/* 不需要单独的结果，只需将 tlist 分配给子计划 */
		fc_plan = fc_subplan;
		fc_plan->targetlist = fc_tlist;

		/* 用我们实际使用的估计成本标记计划 */
		fc_plan->startup_cost = fc_best_path->path.startup_cost;
		fc_plan->total_cost = fc_best_path->path.total_cost;
		fc_plan->plan_rows = fc_best_path->path.rows;
		fc_plan->plan_width = fc_best_path->path.pathtarget->width;
		fc_plan->parallel_safe = fc_best_path->path.parallel_safe;
		/* ... 但不要更改子计划的 parallel_aware 标志 */
	}
	else
	{
		/* 我们需要一个 Result 节点 */
		fc_plan = (Plan *) fc_make_result(fc_tlist, NULL, fc_subplan);

		fc_copy_generic_path_info(fc_plan, (Path *) fc_best_path);
	}

	return fc_plan;
}

/*
 * inject_projection_plan
 *	  插入一个 Result 节点以执行投影步骤。
 *
 * 这在几个地方使用，当我们动态决定需要一个
 * 投影步骤作为某些 Path 节点生成的树的一部分。
 * 我们应该努力摆脱这种方法，转而采用更诚实的做法。
 *
 * 其一个原因是丑陋的是我们必须被告知要应用的正确
 * parallel_safe 标记（因为即使子计划是安全的，
 * tlist 也可能是不安全的）。
 */
static Plan * fc_inject_projection_plan(Plan *fc_subplan, List *fc_tlist, bool fc_parallel_safe)
{
	Plan	   *fc_plan;

	fc_plan = (Plan *) fc_make_result(fc_tlist, NULL, fc_subplan);

	/*
	 *原则上，我们应该对 Result 节点收取 tlist 评估成本
	 * 加上 cpu_per_tuple 每行费用。但前者可能已经被考虑在内
	 * ，而后者在 Path 构造过程中未被计算，因此正式的正确可能
	 * 只会使 EXPLAIN 输出显得不那么一致，而不是更加一致。
	 * 因此，只需复制子计划的成本。
	 */
	fc_copy_plan_costsize(fc_plan, fc_subplan);
	fc_plan->parallel_safe = fc_parallel_safe;

	return fc_plan;
}

/*
 * change_plan_targetlist
 *	  inject_projection_plan 的外部可用封装。
 *
 * 这对于 FDW 计划生成函数使用，可能
 * 想要调整某些子计划树计算的 tlist。一般来说，
 * 需要一个 Result 节点来计算新的 tlist，但我们可以优化
 * 一些情况。
 *
 * 在大多数情况下，tlist_parallel_safe 可以直接传递为
 * FDW 自己的 Path 节点的 parallel_safe 标志。
 */
Plan * change_plan_targetlist(Plan *fc_subplan, List *fc_tlist, bool fc_tlist_parallel_safe)
{
	
/*
	 * 如果顶层计划节点无法进行投影，并且其现有目标列表
	 * 还不是我们所需的，那么我们需要添加一个结果节点来帮助它
	 * 进行处理。
	 */
	if (!is_projection_capable_plan(fc_subplan) &&
		!tlist_same_exprs(fc_tlist, fc_subplan->targetlist))
		fc_subplan = fc_inject_projection_plan(fc_subplan, fc_tlist,
										 fc_subplan->parallel_safe &&
										 fc_tlist_parallel_safe);
	else
	{
		/* 否则我们可以直接替换计划节点的目标列表 */
		fc_subplan->targetlist = fc_tlist;
		fc_subplan->parallel_safe &= fc_tlist_parallel_safe;
	}
	return fc_subplan;
}

/*
 * create_sort_plan
 *
 *	  为 'best_path' 创建一个排序计划，并为其子路径
 *	  递归地创建计划。
 */
static Sort * fc_create_sort_plan(PlannerInfo *fc_root, SortPath *fc_best_path, int fc_flags)
{
	Sort	   *fc_plan;
	Plan	   *fc_subplan;

	/*
	 * 我们不希望在排序元组中有多余的列，因此请求一个
	 * 较小的目标列表。否则，由于 Sort 不进行投影，目标列表
	 * 要求将会传递。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  fc_flags | CP_SMALL_TLIST);

	/*
	 * make_sort_from_pathkeys 间接调用 find_ec_member_matching_expr，
	 * 它将忽略任何不属于给定
	 * relids 的子 EC 成员。因此，如果这个排序路径是基于一个子关系，我们必须
	 * 传递其 relids。
	 */
	fc_plan = fc_make_sort_from_pathkeys(fc_subplan, fc_best_path->path.pathkeys,
								   IS_OTHER_REL(fc_best_path->subpath->parent) ?
								   fc_best_path->path.parent->relids : NULL);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_incrementalsort_plan
 *
 *	  做与 create_sort_plan 相同的事情，但创建 IncrementalSort 计划。
 */
static IncrementalSort * fc_create_incrementalsort_plan(PlannerInfo *fc_root, IncrementalSortPath *fc_best_path,
							int fc_flags)
{
	IncrementalSort *fc_plan;
	Plan	   *fc_subplan;

	/* 请参阅上面 create_sort_plan() 中的注释 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->spath.subpath,
								  fc_flags | CP_SMALL_TLIST);
	fc_plan = fc_make_incrementalsort_from_pathkeys(fc_subplan,
											  fc_best_path->spath.path.pathkeys,
											  IS_OTHER_REL(fc_best_path->spath.subpath->parent) ?
											  fc_best_path->spath.path.parent->relids : NULL,
											  fc_best_path->nPresortedCols);

	fc_copy_generic_path_info(&fc_plan->sort.plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_group_plan
 *
 *	  为 'best_path' 创建一个 Group 计划，并（递归地）为其子路径创建计划。
 */
static Group * fc_create_group_plan(PlannerInfo *fc_root, GroupPath *fc_best_path)
{
	Group	   *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_tlist;
	List	   *fc_quals;

	/*
	 * Group 可以投影，因此不需要对子 tlist 特别挑剔，
	 * 但我们需要分组列可用。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, CP_LABEL_TLIST);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	fc_quals = fc_order_qual_clauses(fc_root, fc_best_path->qual);

	fc_plan = fc_make_group(fc_tlist,
					  fc_quals,
					  list_length(fc_best_path->groupClause),
					  extract_grouping_cols(fc_best_path->groupClause,
											fc_subplan->targetlist),
					  extract_grouping_ops(fc_best_path->groupClause),
					  extract_grouping_collations(fc_best_path->groupClause,
												  fc_subplan->targetlist),
					  fc_subplan);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_upper_unique_plan
 *
 *	  为 'best_path' 创建一个 Unique 计划，并（递归地）为其子路径创建计划。
 */
static Unique * fc_create_upper_unique_plan(PlannerInfo *fc_root, UpperUniquePath *fc_best_path, int fc_flags)
{
	Unique	   *fc_plan;
	Plan	   *fc_subplan;

	/*
	 * Unique 不进行投影，因此 tlist 需求会传递；此外，我们
	 * 需要将分组列标记。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  fc_flags | CP_LABEL_TLIST);

	fc_plan = fc_make_unique_from_pathkeys(fc_subplan,
									 fc_best_path->path.pathkeys,
									 fc_best_path->numkeys);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_agg_plan
 *
 *	  为 'best_path' 创建一个 Agg 计划，并（递归地）为其子路径创建计划。
 */
static Agg * fc_create_agg_plan(PlannerInfo *fc_root, AggPath *fc_best_path)
{
	Agg		   *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_tlist;
	List	   *fc_quals;

	/*
	 * Agg 可以投影，因此不需要对子 tlist 特别挑剔，但
	 * 我们需要分组列可用。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, CP_LABEL_TLIST);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	fc_quals = fc_order_qual_clauses(fc_root, fc_best_path->qual);

	fc_plan = make_agg(fc_tlist, fc_quals,
					fc_best_path->aggstrategy,
					fc_best_path->aggsplit,
					list_length(fc_best_path->groupClause),
					extract_grouping_cols(fc_best_path->groupClause,
										  fc_subplan->targetlist),
					extract_grouping_ops(fc_best_path->groupClause),
					extract_grouping_collations(fc_best_path->groupClause,
												fc_subplan->targetlist),
					NIL,
					NIL,
					fc_best_path->numGroups,
					fc_best_path->transitionSpace,
					fc_subplan);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * 给定一组分组集的 groupclause，生成相应的 groupColIdx。
 *
 * root->grouping_map 将 tleSortGroupRef 映射到输入元组中的实际列位置。
 * 因此，我们从 groupclause 中的条目获取引用并在此查找它们。
 */
static AttrNumber *
fc_remap_groupColIdx(PlannerInfo *fc_root, List *fc_groupClause)
{
	AttrNumber *fc_grouping_map = fc_root->grouping_map;
	AttrNumber *fc_new_grpColIdx;
	ListCell   *fc_lc;
	int			fc_i;

	Assert(fc_grouping_map);

	fc_new_grpColIdx = palloc0(sizeof(AttrNumber) * list_length(fc_groupClause));

	fc_i = 0;
	foreach(fc_lc, fc_groupClause)
	{
		SortGroupClause *fc_clause = lfirst(fc_lc);

		fc_new_grpColIdx[fc_i++] = fc_grouping_map[fc_clause->tleSortGroupRef];
	}

	return fc_new_grpColIdx;
}

/*
 * create_groupingsets_plan
 *	  为 'best_path' 创建一个计划，并（递归地）为其子路径创建计划。
 *
 *	  我们发出的是一个带有一些残余 Agg 和 Sort 节点挂在旁边的 Agg 计划。
 *	  顶部 Agg 实现了在 GroupingSetsPath 中指定的最后一个分组集，
 *	  任何额外的分组集都会在顶部 Agg 的“链”列表中产生一个附属 Agg 和 Sort 节点。
 *	  这些节点不会直接参与计划，
 *	  但它们是表示额外步骤所需数据的方便方式。
 *
 *	  返回一个计划节点。
 */
static Plan * fc_create_groupingsets_plan(PlannerInfo *fc_root, GroupingSetsPath *fc_best_path)
{
	Agg		   *fc_plan;
	Plan	   *fc_subplan;
	List	   *fc_rollups = fc_best_path->rollups;
	AttrNumber *fc_grouping_map;
	int			fc_maxref;
	List	   *fc_chain;
	ListCell   *fc_lc;

	/* 不应该没有分组集就到这里 */
	Assert(fc_root->parse->groupingSets);
	Assert(fc_rollups != NIL);

	/*
	 * Agg 可以投影，因此不需要对子 tlist 特别挑剔，但
	 * 我们需要分组列可用。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, CP_LABEL_TLIST);

	/*
	 * 计算从 tleSortGroupRef 到子 tlist 中列索引的映射。首先，
	 * 确定 groupClause 中的最大 SortGroupRef，以便进行数组大小调整。
	 */
	fc_maxref = 0;
	foreach(fc_lc, fc_root->parse->groupClause)
	{
		SortGroupClause *fc_gc = (SortGroupClause *) lfirst(fc_lc);

		if (fc_gc->tleSortGroupRef > fc_maxref)
			fc_maxref = fc_gc->tleSortGroupRef;
	}

	fc_grouping_map = (AttrNumber *) palloc0((fc_maxref + 1) * sizeof(AttrNumber));

	/* 现在在子 tlist 中查找列号 */
	foreach(fc_lc, fc_root->parse->groupClause)
	{
		SortGroupClause *fc_gc = (SortGroupClause *) lfirst(fc_lc);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_gc, fc_subplan->targetlist);

		fc_grouping_map[fc_gc->tleSortGroupRef] = fc_tle->resno;
	}

	/*
	 * 在 setrefs.c 中，我们需要 grouping_map 来修正 GroupingFunc 节点中的列列表。
	 * 将其保存以供 setrefs.c 使用。
	 */
	Assert(fc_root->grouping_map == NULL);
	fc_root->grouping_map = fc_grouping_map;

	/*
	 * 生成描述顶部以外其他排序和分组操作的侧面节点。
	 * 注意，我们不担心在侧面节点中放置准确的成本估计；
	 * 只有最上面的 Agg 节点的成本会被 EXPLAIN 显示。
	 */
	fc_chain = NIL;
	if (list_length(fc_rollups) > 1)
	{
		bool		fc_is_first_sort = ((RollupData *) linitial(fc_rollups))->is_hashed;

		for_each_from(fc_lc, fc_rollups, 1)
		{
			RollupData *fc_rollup = lfirst(fc_lc);
			AttrNumber *fc_new_grpColIdx;
			Plan	   *sort_plan = NULL;
			Plan	   *fc_agg_plan;
			AggStrategy fc_strat;

			fc_new_grpColIdx = fc_remap_groupColIdx(fc_root, fc_rollup->groupClause);

			if (!fc_rollup->is_hashed && !fc_is_first_sort)
			{
				sort_plan = (Plan *)
					fc_make_sort_from_groupcols(fc_rollup->groupClause,
											 fc_new_grpColIdx,
											 fc_subplan);
			}

			if (!fc_rollup->is_hashed)
				fc_is_first_sort = false;

			if (fc_rollup->is_hashed)
				fc_strat = AGG_HASHED;
			else if (list_length(linitial(fc_rollup->gsets)) == 0)
				fc_strat = AGG_PLAIN;
			else
				fc_strat = AGG_SORTED;

			fc_agg_plan = (Plan *) make_agg(NIL,
										 NIL,
										 fc_strat,
										 AGGSPLIT_SIMPLE,
										 list_length((List *) linitial(fc_rollup->gsets)),
										 fc_new_grpColIdx,
										 extract_grouping_ops(fc_rollup->groupClause),
										 extract_grouping_collations(fc_rollup->groupClause, fc_subplan->targetlist),
										 fc_rollup->gsets,
										 NIL,
										 fc_rollup->numGroups,
										 fc_best_path->transitionSpace,
										 sort_plan);

			/*
			 * 移除我们不需要的东西，以避免调试输出膨胀。
			 */
			if (sort_plan)
			{
				sort_plan->targetlist = NIL;
				sort_plan->lefttree = NULL;
			}

			fc_chain = lappend(fc_chain, fc_agg_plan);
		}
	}

	/*
	 * 现在创建真正的 Agg 节点
	 */
	{
		RollupData *fc_rollup = linitial(fc_rollups);
		AttrNumber *fc_top_grpColIdx;
		int			fc_numGroupCols;

		fc_top_grpColIdx = fc_remap_groupColIdx(fc_root, fc_rollup->groupClause);

		fc_numGroupCols = list_length((List *) linitial(fc_rollup->gsets));

		fc_plan = make_agg(fc_build_path_tlist(fc_root, &fc_best_path->path),
						fc_best_path->qual,
						fc_best_path->aggstrategy,
						AGGSPLIT_SIMPLE,
						fc_numGroupCols,
						fc_top_grpColIdx,
						extract_grouping_ops(fc_rollup->groupClause),
						extract_grouping_collations(fc_rollup->groupClause, fc_subplan->targetlist),
						fc_rollup->gsets,
						fc_chain,
						fc_rollup->numGroups,
						fc_best_path->transitionSpace,
						fc_subplan);

		/* 从 Path 复制成本数据到 Plan */
		fc_copy_generic_path_info(&fc_plan->plan, &fc_best_path->path);
	}

	return (Plan *) fc_plan;
}

/*
 * create_minmaxagg_plan
 *
 *	  为 'best_path' 创建一个 Result 计划，并（递归地）为其子路径创建计划。
 */
static Result * fc_create_minmaxagg_plan(PlannerInfo *fc_root, MinMaxAggPath *fc_best_path)
{
	Result	   *fc_plan;
	List	   *fc_tlist;
	ListCell   *fc_lc;

	/* 为每个聚合的子查询准备一个 InitPlan。 */
	foreach(fc_lc, fc_best_path->mmaggregates)
	{
		MinMaxAggInfo *fc_mminfo = (MinMaxAggInfo *) lfirst(fc_lc);
		PlannerInfo *fc_subroot = fc_mminfo->subroot;
		Query	   *fc_subparse = fc_subroot->parse;
		Plan	   *fc_plan;

		/*
		 * 为子查询生成计划。我们已经有一个 Path，但必须将其转换为 Plan，并在其上方附加一个 LIMIT 节点。
		 * 由于我们进入了一个不同的规划上下文（subroot），
		 * 递归调用创建 create_plan 而不是 create_plan_recurse。
		 */
		fc_plan = create_plan(fc_subroot, fc_mminfo->path);

		fc_plan = (Plan *) make_limit(fc_plan,
								   fc_subparse->limitOffset,
								   fc_subparse->limitCount,
								   fc_subparse->limitOption,
								   0, NULL, NULL, NULL);

		/* 必须将正确的成本/宽度数据应用于 Limit 节点 */
		fc_plan->startup_cost = fc_mminfo->path->startup_cost;
		fc_plan->total_cost = fc_mminfo->pathcost;
		fc_plan->plan_rows = 1;
		fc_plan->plan_width = fc_mminfo->path->pathtarget->width;
		fc_plan->parallel_aware = false;
		fc_plan->parallel_safe = fc_mminfo->path->parallel_safe;

		/* 将计划转换为外部查询中的 InitPlan。 */
		SS_make_initplan_from_plan(fc_root, fc_subroot, fc_plan, fc_mminfo->param);
	}

	/* 生成输出计划 --- 基本上只是一个 Result */
	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	fc_plan = fc_make_result(fc_tlist, (Node *) fc_best_path->quals, NULL);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	/*
	 * 在 setrefs.c 中，我们需要将对 Agg 节点的引用替换为 InitPlan 输出参数。
	 * （我们不能仅在 MinMaxAgg 节点中本地执行此操作，因为上面的路径节点可能也具有 Agg 引用。）
	 * 将 mmaggregates 列表保存以通知 setrefs.c 完成此操作。
	 */
	Assert(fc_root->minmax_aggs == NIL);
	fc_root->minmax_aggs = fc_best_path->mmaggregates;

	return fc_plan;
}

/*
 * create_windowagg_plan
 *
 *	  为 'best_path' 创建一个 WindowAgg 计划，并（递归地）为其子路径创建计划。
 */
static WindowAgg * fc_create_windowagg_plan(PlannerInfo *fc_root, WindowAggPath *fc_best_path)
{
	WindowAgg  *fc_plan;
	WindowClause *fc_wc = fc_best_path->winclause;
	int			fc_numPart = list_length(fc_wc->partitionClause);
	int			fc_numOrder = list_length(fc_wc->orderClause);
	Plan	   *fc_subplan;
	List	   *fc_tlist;
	int			fc_partNumCols;
	AttrNumber *fc_partColIdx;
	Oid		   *fc_partOperators;
	Oid		   *fc_partCollations;
	int			fc_ordNumCols;
	AttrNumber *fc_ordColIdx;
	Oid		   *fc_ordOperators;
	Oid		   *fc_ordCollations;
	ListCell   *fc_lc;

	/*
	 * 此处的 tlist 选择是由于 WindowAgg 将在 tuplestore 中存储窗口帧的输入行；
	 * 因此，我们要求小的 tlist 以避免浪费空间。当然，我们需要分组列可以用。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  CP_LABEL_TLIST | CP_SMALL_TLIST);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	/*
	 * 将 SortGroupClause 列表转换为所需的属性索引和相等操作符数组，正如执行器所需。
	 * （注意：原则上，如果路径键逻辑证明某些排序列冗余，可以丢弃它们。
	 * 但是，似乎不值得特意优化这种情况。无论如何，我们必须 *不* 删除 RANGE OFFSET 情况的排序列，
	 * 因为执行器需要它用于 in_range 测试，即使它已知等于某些分区列。）
	 */
	fc_partColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numPart);
	fc_partOperators = (Oid *) palloc(sizeof(Oid) * fc_numPart);
	fc_partCollations = (Oid *) palloc(sizeof(Oid) * fc_numPart);

	fc_partNumCols = 0;
	foreach(fc_lc, fc_wc->partitionClause)
	{
		SortGroupClause *fc_sgc = (SortGroupClause *) lfirst(fc_lc);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sgc, fc_subplan->targetlist);

		Assert(OidIsValid(fc_sgc->eqop));
		fc_partColIdx[fc_partNumCols] = fc_tle->resno;
		fc_partOperators[fc_partNumCols] = fc_sgc->eqop;
		fc_partCollations[fc_partNumCols] = exprCollation((Node *) fc_tle->expr);
		fc_partNumCols++;
	}

	fc_ordColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numOrder);
	fc_ordOperators = (Oid *) palloc(sizeof(Oid) * fc_numOrder);
	fc_ordCollations = (Oid *) palloc(sizeof(Oid) * fc_numOrder);

	fc_ordNumCols = 0;
	foreach(fc_lc, fc_wc->orderClause)
	{
		SortGroupClause *fc_sgc = (SortGroupClause *) lfirst(fc_lc);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sgc, fc_subplan->targetlist);

		Assert(OidIsValid(fc_sgc->eqop));
		fc_ordColIdx[fc_ordNumCols] = fc_tle->resno;
		fc_ordOperators[fc_ordNumCols] = fc_sgc->eqop;
		fc_ordCollations[fc_ordNumCols] = exprCollation((Node *) fc_tle->expr);
		fc_ordNumCols++;
	}

	/* 最后，我们可以创建 WindowAgg 节点 */
	fc_plan = fc_make_windowagg(fc_tlist,
						  fc_wc->winref,
						  fc_partNumCols,
						  fc_partColIdx,
						  fc_partOperators,
						  fc_partCollations,
						  fc_ordNumCols,
						  fc_ordColIdx,
						  fc_ordOperators,
						  fc_ordCollations,
						  fc_wc->frameOptions,
						  fc_wc->startOffset,
						  fc_wc->endOffset,
						  fc_wc->startInRangeFunc,
						  fc_wc->endInRangeFunc,
						  fc_wc->inRangeColl,
						  fc_wc->inRangeAsc,
						  fc_wc->inRangeNullsFirst,
						  fc_wc->runCondition,
						  fc_best_path->qual,
						  fc_best_path->topwindow,
						  fc_subplan);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_setop_plan
 *
 *	  为 'best_path' 创建一个 SetOp 计划，并（递归地）为其子路径创建计划。
 */
static SetOp * fc_create_setop_plan(PlannerInfo *fc_root, SetOpPath *fc_best_path, int fc_flags)
{
	SetOp	   *fc_plan;
	Plan	   *fc_subplan;
	long		fc_numGroups;

	/*
	 * SetOp 不进行投影，因此 tlist 需求会传递；此外，我们
	 * 需要将分组列标记。
	 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath,
								  fc_flags | CP_LABEL_TLIST);

	/* 将 numGroups 转换为长整型 --- 但要小心溢出！ */
	fc_numGroups = clamp_cardinality_to_long(fc_best_path->numGroups);

	fc_plan = fc_make_setop(fc_best_path->cmd,
					  fc_best_path->strategy,
					  fc_subplan,
					  fc_best_path->distinctList,
					  fc_best_path->flagColIdx,
					  fc_best_path->firstFlag,
					  fc_numGroups);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_recursiveunion_plan
 *
 *	  为 'best_path' 创建一个 RecursiveUnion 计划，并递归地为其子路径创建计划。
 */
static RecursiveUnion * fc_create_recursiveunion_plan(PlannerInfo *fc_root, RecursiveUnionPath *fc_best_path)
{
	RecursiveUnion *fc_plan;
	Plan	   *fc_leftplan;
	Plan	   *fc_rightplan;
	List	   *fc_tlist;
	long		fc_numGroups;

	/* 需要两个子节点产生相同的 tlist，因此强制要求 */
	fc_leftplan = fc_create_plan_recurse(fc_root, fc_best_path->leftpath, CP_EXACT_TLIST);
	fc_rightplan = fc_create_plan_recurse(fc_root, fc_best_path->rightpath, CP_EXACT_TLIST);

	fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->path);

	/* 将 numGroups 转换为长整型 --- 但要小心溢出！ */
	fc_numGroups = clamp_cardinality_to_long(fc_best_path->numGroups);

	fc_plan = fc_make_recursive_union(fc_tlist,
								fc_leftplan,
								fc_rightplan,
								fc_best_path->wtParam,
								fc_best_path->distinctList,
								fc_numGroups);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_lockrows_plan
 *
 *	  为 'best_path' 创建一个 LockRows 计划，并递归地为其子路径创建计划。
 */
static LockRows * fc_create_lockrows_plan(PlannerInfo *fc_root, LockRowsPath *fc_best_path,
					 int fc_flags)
{
	LockRows   *fc_plan;
	Plan	   *fc_subplan;

	/* LockRows 不进行投影，因此 tlist 要求会通过 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, fc_flags);

	fc_plan = fc_make_lockrows(fc_subplan, fc_best_path->rowMarks, fc_best_path->epqParam);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}

/*
 * create_modifytable_plan
 *	  为 'best_path' 创建一个 ModifyTable 计划。
 *
 *	  返回一个 Plan 节点。
 */
static ModifyTable * fc_create_modifytable_plan(PlannerInfo *fc_root, ModifyTablePath *fc_best_path)
{
	ModifyTable *fc_plan;
	Path	   *fc_subpath = fc_best_path->subpath;
	Plan	   *fc_subplan;

	/* 子计划必须准确生成指定的 tlist */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_subpath, CP_EXACT_TLIST);

	/* 还要转移 resname/resjunk 标签，以满足执行器的要求 */
	apply_tlist_labeling(fc_subplan->targetlist, fc_root->processed_tlist);

	fc_plan = fc_make_modifytable(fc_root,
							fc_subplan,
							fc_best_path->operation,
							fc_best_path->canSetTag,
							fc_best_path->nominalRelation,
							fc_best_path->rootRelation,
							fc_best_path->partColsUpdated,
							fc_best_path->resultRelations,
							fc_best_path->updateColnosLists,
							fc_best_path->withCheckOptionLists,
							fc_best_path->returningLists,
							fc_best_path->rowMarks,
							fc_best_path->onconflict,
							fc_best_path->mergeActionLists,
							fc_best_path->epqParam);

	fc_copy_generic_path_info(&fc_plan->plan, &fc_best_path->path);

	return fc_plan;
}

/*
 * create_limit_plan
 *
 *	  为 'best_path' 创建一个 Limit 计划，并递归地为其子路径创建计划。
 */
static Limit * fc_create_limit_plan(PlannerInfo *fc_root, LimitPath *fc_best_path, int fc_flags)
{
	Limit	   *fc_plan;
	Plan	   *fc_subplan;
	int			fc_numUniqkeys = 0;
	AttrNumber *fc_uniqColIdx = NULL;
	Oid		   *fc_uniqOperators = NULL;
	Oid		   *fc_uniqCollations = NULL;

	/* Limit 不进行投影，因此 tlist 要求会通过 */
	fc_subplan = fc_create_plan_recurse(fc_root, fc_best_path->subpath, fc_flags);

	/* 提取比较行以进行 WITH TIES 所需的信息。 */
	if (fc_best_path->limitOption == LIMIT_OPTION_WITH_TIES)
	{
		Query	   *fc_parse = fc_root->parse;
		ListCell   *fc_l;

		fc_numUniqkeys = list_length(fc_parse->sortClause);
		fc_uniqColIdx = (AttrNumber *) palloc(fc_numUniqkeys * sizeof(AttrNumber));
		fc_uniqOperators = (Oid *) palloc(fc_numUniqkeys * sizeof(Oid));
		fc_uniqCollations = (Oid *) palloc(fc_numUniqkeys * sizeof(Oid));

		fc_numUniqkeys = 0;
		foreach(fc_l, fc_parse->sortClause)
		{
			SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_l);
			TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl, fc_parse->targetList);

			fc_uniqColIdx[fc_numUniqkeys] = fc_tle->resno;
			fc_uniqOperators[fc_numUniqkeys] = fc_sortcl->eqop;
			fc_uniqCollations[fc_numUniqkeys] = exprCollation((Node *) fc_tle->expr);
			fc_numUniqkeys++;
		}
	}

	fc_plan = make_limit(fc_subplan,
					  fc_best_path->limitOffset,
					  fc_best_path->limitCount,
					  fc_best_path->limitOption,
					  fc_numUniqkeys, fc_uniqColIdx, fc_uniqOperators, fc_uniqCollations);

	fc_copy_generic_path_info(&fc_plan->plan, (Path *) fc_best_path);

	return fc_plan;
}


/*****************************************************************************
 *
 *	基础关系扫描方法
 *
 *****************************************************************************/


/*
 * create_seqscan_plan
 *	 返回一个 seqscan 计划，用于 'best_path' 扫描的基础关系，
 *	 具有限制子句 'scan_clauses' 和 目标列表 'tlist'。
 */
static SeqScan * fc_create_seqscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
					List *fc_tlist, List *fc_scan_clauses)
{
	SeqScan    *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;

	/* 它应该是一个基础关系... */
	Assert(fc_scan_relid > 0);
	Assert(fc_best_path->parent->rtekind == RTE_RELATION);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_seqscan(fc_tlist,
							 fc_scan_clauses,
							 fc_scan_relid);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_samplescan_plan
 *	 返回一个 samplescan 计划，用于 'best_path' 扫描的基础关系，
 *	 具有限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static SampleScan * fc_create_samplescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
					   List *fc_tlist, List *fc_scan_clauses)
{
	SampleScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	TableSampleClause *fc_tsc;

	/* 它应该是拥有 tablesample 子句的基础关系... */
	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_RELATION);
	fc_tsc = fc_rte->tablesample;
	Assert(fc_tsc != NULL);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
		fc_tsc = (TableSampleClause *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_tsc);
	}

	fc_scan_plan = fc_make_samplescan(fc_tlist,
								fc_scan_clauses,
								fc_scan_relid,
								fc_tsc);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_indexscan_plan
 *	  返回一个 indexscan 计划，用于 'best_path' 扫描的基础关系，
 *	  具有限制子句 'scan_clauses' 和目标列表 'tlist'。
 *
 * 我们为普通的 IndexScan 和 IndexOnlyScan 都使用此计划，因为
 * 质疑预处理工作对两者都是相同的。请注意，调用者告诉
 * 我们构建哪一个 --- 我们不查看 best_path->path.pathtype，因为
 * create_bitmap_subplan 需要能够覆盖先前的决定。
 */
static Scan * fc_create_indexscan_plan(PlannerInfo *fc_root,
					  IndexPath *fc_best_path,
					  List *fc_tlist,
					  List *fc_scan_clauses,
					  bool fc_indexonly)
{
	Scan	   *fc_scan_plan;
	List	   *fc_indexclauses = fc_best_path->indexclauses;
	List	   *fc_indexorderbys = fc_best_path->indexorderbys;
	Index		fc_baserelid = fc_best_path->path.parent->relid;
	IndexOptInfo *fc_indexinfo = fc_best_path->indexinfo;
	Oid			fc_indexoid = fc_indexinfo->indexoid;
	List	   *fc_qpqual;
	List	   *fc_stripped_indexquals;
	List	   *fc_fixed_indexquals;
	List	   *fc_fixed_indexorderbys;
	List	   *fc_indexorderbyops = NIL;
	ListCell   *fc_l;

	/* 它应该是一个基础关系... */
	Assert(fc_baserelid > 0);
	Assert(fc_best_path->path.parent->rtekind == RTE_RELATION);

	/*
	 * 从 IndexClauses 列表中提取索引质疑表达式（去除 RestrictInfos），
	 * 并准备一个用索引 Vars 替换表 Vars 的副本。
	 * （这一步还会对 fixed_indexquals 执行 replace_nestloop_params。）
	 */
	fc_fix_indexqual_references(fc_root, fc_best_path,
							 &fc_stripped_indexquals,
							 &fc_fixed_indexquals);

	/*
	 * 同样在 ORDER BY 表达式中修正索引属性引用。
	 */
	fc_fixed_indexorderbys = fc_fix_indexorderby_references(fc_root, fc_best_path);

	/*
	 * qpqual 列表必须包含所有未被索引自动处理的限制条件，
	 * 以及将由单独的门控计划节点处理的伪常量子句。
	 * 索引质疑中的所有谓词都将被检查（无论是通过索引本身，还是通过 nodeIndexscan.c），
	 * 但如果涉及到任何“特殊”操作符，则必须将其包含在 qpqual 中。
	 * 结果是 qpqual 必须包含 scan_clauses 减去在 indexquals 中出现的内容。
	 *
	 * is_redundant_with_indexclauses() 检测扫描子句是否
	 * 存在于 indexclauses 列表中或是从某个 indexclause 的同一
	 * EquivalenceClass 生成的，因此与其是冗余的，但不相等。
	 * （后者发生在 indxpath.c 偏好于不同的派生等式时，而不是
	 * generate_join_implied_equalities 为参数化扫描的 ppi_clauses 选择的。）
	 * 请注意，它不会与损失性索引子句匹配，这一点至关重要，
	 * 因为在这种情况下，我们必须在 qpqual 中包含原始子句。
	 *
	 * 在某些情况下（特别是带有 OR 的索引条件），我们可能会
	 * 有与索引质疑不相等但在逻辑上被其暗示的 scan_clauses；
	 * 因此我们还尝试 predicate_implied_by() 检查，以查看
	 * 我们是否可以以这种方式丢弃质疑。（predicate_implied_by 假设
	 * 其第一个输入仅包含不变函数，因此我们必须检查这一点。）
	 *
	 * 注意：如果您更改这段代码，您还应该查看
	 * costsize.c 中的 extract_nonindex_conditions()。
	 */
	fc_qpqual = NIL;
	foreach(fc_l, fc_scan_clauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

		if (fc_rinfo->pseudoconstant)
			continue;			/* 我们可以在这里丢掉伪常量 */
		if (is_redundant_with_indexclauses(fc_rinfo, fc_indexclauses))
			continue;			/* 重复或派生自同一等价类 */
		if (!contain_mutable_functions((Node *) fc_rinfo->clause) &&
			predicate_implied_by(list_make1(fc_rinfo->clause), fc_stripped_indexquals,
								 false))
			continue;			/* 可证明由 indexquals 暗示 */
		fc_qpqual = lappend(fc_qpqual, fc_rinfo);
	}

	/* 将子句排序为最佳执行顺序 */
	fc_qpqual = fc_order_qual_clauses(fc_root, fc_qpqual);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_qpqual = extract_actual_clauses(fc_qpqual, false);

	/*
	 * 我们必须在 indexqualorig、qpqual 和 indexorderbyorig 表达式中
	 * 将任何外部关系变量替换为 nestloop 参数。
	 * 单独进行这项操作有点烦人 -- 在泛化内部
	 * indexscan 支持时重新考虑这一点。但请注意，我们不能在更早的时候
	 * 这样做，因为这会破坏与上述谓词的比较……（或者会吗？这些
	 * 不会有外部引用）
	 */
	if (fc_best_path->path.param_info)
	{
		fc_stripped_indexquals = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_stripped_indexquals);
		fc_qpqual = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_qpqual);
		fc_indexorderbys = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_indexorderbys);
	}

	/*
	 * 如果有 ORDER BY 表达式，请查找其结果数据类型的排序操作符。
	 */
	if (fc_indexorderbys)
	{
		ListCell   *fc_pathkeyCell,
				   *fc_exprCell;

		
/*
		 * PathKey包含我们排序时使用的btree运算族的OID，但这还不够，因为我们需要表达式的数据类型
		 * 以便在运算符族中查找排序运算符。
		 */
		Assert(list_length(fc_best_path->path.pathkeys) == list_length(fc_indexorderbys));
		forboth(fc_pathkeyCell, fc_best_path->path.pathkeys, fc_exprCell, fc_indexorderbys)
		{
			PathKey    *fc_pathkey = (PathKey *) lfirst(fc_pathkeyCell);
			Node	   *fc_expr = (Node *) lfirst(fc_exprCell);
			Oid			fc_exprtype = exprType(fc_expr);
			Oid			fc_sortop;

			/* 从运算族中获取排序运算符 */
			fc_sortop = get_opfamily_member(fc_pathkey->pk_opfamily,
										 fc_exprtype,
										 fc_exprtype,
										 fc_pathkey->pk_strategy);
			if (!OidIsValid(fc_sortop))
				elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
					 fc_pathkey->pk_strategy, fc_exprtype, fc_exprtype, fc_pathkey->pk_opfamily);
			fc_indexorderbyops = lappend_oid(fc_indexorderbyops, fc_sortop);
		}
	}

	/*
	 * 对于仅索引扫描，我们必须将indextlist条目标记为resjunk，如果它们是索引AM无法返回的列；
	 * 这提示setrefs.c不要生成对这些列的引用。
	 */
	if (fc_indexonly)
	{
		int			fc_i = 0;

		foreach(fc_l, fc_indexinfo->indextlist)
		{
			TargetEntry *fc_indextle = (TargetEntry *) lfirst(fc_l);

			fc_indextle->resjunk = !fc_indexinfo->canreturn[fc_i];
			fc_i++;
		}
	}

	/* 最终准备构建计划节点 */
	if (fc_indexonly)
		fc_scan_plan = (Scan *) fc_make_indexonlyscan(fc_tlist,
												fc_qpqual,
												fc_baserelid,
												fc_indexoid,
												fc_fixed_indexquals,
												fc_stripped_indexquals,
												fc_fixed_indexorderbys,
												fc_indexinfo->indextlist,
												fc_best_path->indexscandir);
	else
		fc_scan_plan = (Scan *) fc_make_indexscan(fc_tlist,
											fc_qpqual,
											fc_baserelid,
											fc_indexoid,
											fc_fixed_indexquals,
											fc_stripped_indexquals,
											fc_fixed_indexorderbys,
											fc_indexorderbys,
											fc_indexorderbyops,
											fc_best_path->indexscandir);

	fc_copy_generic_path_info(&fc_scan_plan->plan, &fc_best_path->path);

	return fc_scan_plan;
}

/*
 * create_bitmap_scan_plan
 *	  返回一个用于通过'best_path'扫描的基本关系的位图扫描计划
 *	  以及限制条件'扫描条件'和目标列表'tlist'。
 */
static BitmapHeapScan * fc_create_bitmap_scan_plan(PlannerInfo *fc_root,
						BitmapHeapPath *fc_best_path,
						List *fc_tlist,
						List *fc_scan_clauses)
{
	Index		fc_baserelid = fc_best_path->path.parent->relid;
	Plan	   *fc_bitmapqualplan;
	List	   *fc_bitmapqualorig;
	List	   *fc_indexquals;
	List	   *fc_indexECs;
	List	   *fc_qpqual;
	ListCell   *fc_l;
	BitmapHeapScan *fc_scan_plan;

	/* 它应该是一个基础关系... */
	Assert(fc_baserelid > 0);
	Assert(fc_best_path->path.parent->rtekind == RTE_RELATION);

	/**** 处理位图qual树为计划树和qual列表 ****/
	fc_bitmapqualplan = fc_create_bitmap_subplan(fc_root, fc_best_path->bitmapqual,
										   &fc_bitmapqualorig, &fc_indexquals,
										   &fc_indexECs);

	if (fc_best_path->path.parallel_aware)
		fc_bitmap_subplan_mark_shared(fc_bitmapqualplan);

	/*
	 * qpqual列表必须包含所有未被索引自动处理的限制，除了将由
	 * 单独的门控计划节点处理的伪常量子句。索引qual中的所有谓词
	 * 都会被检查（要么由索引本身，要么由nodeBitmapHeapscan.c），
	 * 但如果涉及任何“特殊”运算符，则必须将其添加到qpqual中。
	 * 明白的一点是，qpqual必须包含scan_clauses减去出现在indexquals中的项。
	 *
	 * 这个循环与create_indexscan_plan()中的相应代码类似，
	 * 但有一些区别，因为它必须将scan子句与去掉（无RestrictInfos）的
	 * indexquals进行比较。有关更多信息，请参阅那里注释。
	 *
	 * 在正常情况下，简单的equal()检查足以发现重复子句，
	 * 所以我们优先尝试这一点。接下来，我们查看scan子句是否
	 * 由于由相同的EC生成而与任何顶层indexqual冗余。之后，尝试
	 * predicate_implied_by()。
	 *
	 * 与create_indexscan_plan()不同，这里predicate_implied_by()的
	 * 测试对剔除由索引谓词暗示的qpquals是有用的，因为谓词条件
	 * 包括在create_bitmap_subplan()返回的“indexquals”中。
	 * 位图扫描必须这样处理，因为如果扫描变得损失性，就需要
	 * 重新检查谓词条件，因此必须将其包含在bitmapqualorig中。
	 */
	fc_qpqual = NIL;
	foreach(fc_l, fc_scan_clauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);
		Node	   *fc_clause = (Node *) fc_rinfo->clause;

		if (fc_rinfo->pseudoconstant)
			continue;			/* 我们可以在这里丢掉伪常量 */
		if (list_member(fc_indexquals, fc_clause))
			continue;			/* 简单重复 */
		if (fc_rinfo->parent_ec && list_member_ptr(fc_indexECs, fc_rinfo->parent_ec))
			continue;			/* 来源于相同的等价类 */
		if (!contain_mutable_functions(fc_clause) &&
			predicate_implied_by(list_make1(fc_clause), fc_indexquals, false))
			continue;			/* 可证明由 indexquals 暗示 */
		fc_qpqual = lappend(fc_qpqual, fc_rinfo);
	}

	/* 将子句排序为最佳执行顺序 */
	fc_qpqual = fc_order_qual_clauses(fc_root, fc_qpqual);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_qpqual = extract_actual_clauses(fc_qpqual, false);

	/*
	 * 在处理特殊运算符时，此时会在qpqual和bitmapqualorig中
	 * 存在重复子句。我们不妨从bitmapqualorig中删除它们，
	 * 因为没有必要进行两次测试。
	 */
	fc_bitmapqualorig = list_difference_ptr(fc_bitmapqualorig, fc_qpqual);

	/*
	 * 我们必须在qpqual和bitmapqualorig表达式中用嵌套循环参数替换
	 * 任何外部关系变量。（这已经针对位图qual计划树中的计划节点
	 * 附加的表达式完成。）
	 */
	if (fc_best_path->path.param_info)
	{
		fc_qpqual = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_qpqual);
		fc_bitmapqualorig = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_bitmapqualorig);
	}

	/* 最终准备构建计划节点 */
	fc_scan_plan = fc_make_bitmap_heapscan(fc_tlist,
									 fc_qpqual,
									 fc_bitmapqualplan,
									 fc_bitmapqualorig,
									 fc_baserelid);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, &fc_best_path->path);

	return fc_scan_plan;
}

/*
 * 给定位图qual树，生成实现它的计划树
 *
 * 同时，我们还在*qual和*indexqual中返回描述原始索引条件和
 * 生成的索引qual条件的qual列表（以隐式-AND形式，无RestrictInfos）。
 * （在简单的情况下，这两个列表是相同的，但当涉及特殊索引运算符时，
 * 前者列表包含特殊条件，而后者包括从中导出的实际可索引条件。）
 * 两个列表都包括部分索引谓词，因为如果位图扫描变得损失性，
 * 我们就必须重新检查谓词以及索引条件。
 *
 * 此外，我们返回一个指向所有可能冗余由EC派生的顶层indexquals的
 * 等价类指针列表。这允许去除与这些quals冗余的scan_clauses。
 * （我们不尝试检测在OR子树中冗余的quals。若以RestrictInfo形式返回
 * indexquals，可能可以以更少的黑客方式完成，但那样会更慢，
 * 并且仍旧相当混乱，因为在许多情况下我们必须构建新的RestrictInfos。）
 */
static Plan * fc_create_bitmap_subplan(PlannerInfo *fc_root, Path *fc_bitmapqual,
					  List **fc_qual, List **fc_indexqual, List **fc_indexECs)
{
	Plan	   *fc_plan;

	if (IsA(fc_bitmapqual, BitmapAndPath))
	{
		BitmapAndPath *fc_apath = (BitmapAndPath *) fc_bitmapqual;
		List	   *fc_subplans = NIL;
		List	   *fc_subquals = NIL;
		List	   *fc_subindexquals = NIL;
		List	   *fc_subindexECs = NIL;
		ListCell   *fc_l;

		/*
		 * 子计划中可能会有冗余的quals，因为顶层WHERE qual可能
		 * 已被用于形成几个不同的索引quals。我们并不试图过于努力地
		 * 消除冗余，但我们确实通过使用list_concat_unique来消除
		 * 显而易见的重复项。
		 */
		foreach(fc_l, fc_apath->bitmapquals)
		{
			Plan	   *fc_subplan;
			List	   *fc_subqual;
			List	   *fc_subindexqual;
			List	   *fc_subindexEC;

			fc_subplan = fc_create_bitmap_subplan(fc_root, (Path *) lfirst(fc_l),
											&fc_subqual, &fc_subindexqual,
											&fc_subindexEC);
			fc_subplans = lappend(fc_subplans, fc_subplan);
			fc_subquals = list_concat_unique(fc_subquals, fc_subqual);
			fc_subindexquals = list_concat_unique(fc_subindexquals, fc_subindexqual);
			/* 索引EC中的重复项不值得去除 */
			fc_subindexECs = list_concat(fc_subindexECs, fc_subindexEC);
		}
		fc_plan = (Plan *) fc_make_bitmap_and(fc_subplans);
		fc_plan->startup_cost = fc_apath->path.startup_cost;
		fc_plan->total_cost = fc_apath->path.total_cost;
		fc_plan->plan_rows =
			clamp_row_est(fc_apath->bitmapselectivity * fc_apath->path.parent->tuples);
		fc_plan->plan_width = 0;	/* 毫无意义 */
		fc_plan->parallel_aware = false;
		fc_plan->parallel_safe = fc_apath->path.parallel_safe;
		*fc_qual = fc_subquals;
		*fc_indexqual = fc_subindexquals;
		*fc_indexECs = fc_subindexECs;
	}
	else if (IsA(fc_bitmapqual, BitmapOrPath))
	{
		BitmapOrPath *fc_opath = (BitmapOrPath *) fc_bitmapqual;
		List	   *fc_subplans = NIL;
		List	   *fc_subquals = NIL;
		List	   *fc_subindexquals = NIL;
		bool		fc_const_true_subqual = false;
		bool		fc_const_true_subindexqual = false;
		ListCell   *fc_l;

		/*
		 * 在这里，我们仅检测无qual的子计划。无qual的子计划会
		 * 导致我们生成“... OR true ...”，我们完全可以将其简化为
		 * 仅“true”。我们不尝试消除冗余的子子句，因为（a）这并不
		 * 如AND情况那样可能，并且（b）我们可能正在处理数百甚至
		 * 数千个OR条件，可能来自一个长长的IN列表。list_append_unique的
		 * 性能将是不可接受的。
		 */
		foreach(fc_l, fc_opath->bitmapquals)
		{
			Plan	   *fc_subplan;
			List	   *fc_subqual;
			List	   *fc_subindexqual;
			List	   *fc_subindexEC;

			fc_subplan = fc_create_bitmap_subplan(fc_root, (Path *) lfirst(fc_l),
											&fc_subqual, &fc_subindexqual,
											&fc_subindexEC);
			fc_subplans = lappend(fc_subplans, fc_subplan);
			if (fc_subqual == NIL)
				fc_const_true_subqual = true;
			else if (!fc_const_true_subqual)
				fc_subquals = lappend(fc_subquals,
								   make_ands_explicit(fc_subqual));
			if (fc_subindexqual == NIL)
				fc_const_true_subindexqual = true;
			else if (!fc_const_true_subindexqual)
				fc_subindexquals = lappend(fc_subindexquals,
										make_ands_explicit(fc_subindexqual));
		}

		/*
		 * 在存在ScalarArrayOpExpr quals的情况下，我们可能已经构建
		 * 了仅有一个子路径的BitmapOrPaths；不要添加OR步骤。
		 */
		if (list_length(fc_subplans) == 1)
		{
			fc_plan = (Plan *) linitial(fc_subplans);
		}
		else
		{
			fc_plan = (Plan *) fc_make_bitmap_or(fc_subplans);
			fc_plan->startup_cost = fc_opath->path.startup_cost;
			fc_plan->total_cost = fc_opath->path.total_cost;
			fc_plan->plan_rows =
				clamp_row_est(fc_opath->bitmapselectivity * fc_opath->path.parent->tuples);
			fc_plan->plan_width = 0;	/* 毫无意义 */
			fc_plan->parallel_aware = false;
			fc_plan->parallel_safe = fc_opath->path.parallel_safe;
		}

		/*
		 * 如果存在常量-TRUE的子qual，OR将简化为常量TRUE。
		 * 此外，避免生成单元素的OR，这可能是由于冗余消除或
		 * ScalarArrayOpExpr quals而发生的。
		 */
		if (fc_const_true_subqual)
			*fc_qual = NIL;
		else if (list_length(fc_subquals) <= 1)
			*fc_qual = fc_subquals;
		else
			*fc_qual = list_make1(make_orclause(fc_subquals));
		if (fc_const_true_subindexqual)
			*fc_indexqual = NIL;
		else if (list_length(fc_subindexquals) <= 1)
			*fc_indexqual = fc_subindexquals;
		else
			*fc_indexqual = list_make1(make_orclause(fc_subindexquals));
		*fc_indexECs = NIL;
	}
	else if (IsA(fc_bitmapqual, IndexPath))
	{
		IndexPath  *fc_ipath = (IndexPath *) fc_bitmapqual;
		IndexScan  *fc_iscan;
		List	   *fc_subquals;
		List	   *fc_subindexquals;
		List	   *fc_subindexECs;
		ListCell   *fc_l;

		/* 使用常规的indexscan计划构建机制... */
		fc_iscan = castNode(IndexScan,
						 fc_create_indexscan_plan(fc_root, fc_ipath,
											   NIL, NIL, false));
		/* 然后转换为位图索引扫描 */
		fc_plan = (Plan *) fc_make_bitmap_indexscan(fc_iscan->scan.scanrelid,
											  fc_iscan->indexid,
											  fc_iscan->indexqual,
											  fc_iscan->indexqualorig);
		/* 并适当地设置其成本/宽度字段 */
		fc_plan->startup_cost = 0.0;
		fc_plan->total_cost = fc_ipath->indextotalcost;
		fc_plan->plan_rows =
			clamp_row_est(fc_ipath->indexselectivity * fc_ipath->path.parent->tuples);
		fc_plan->plan_width = 0;	/* 毫无意义 */
		fc_plan->parallel_aware = false;
		fc_plan->parallel_safe = fc_ipath->path.parallel_safe;
		/* 提取原始索引子句、实际索引quals、相关ECs */
		fc_subquals = NIL;
		fc_subindexquals = NIL;
		fc_subindexECs = NIL;
		foreach(fc_l, fc_ipath->indexclauses)
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_l);
			RestrictInfo *fc_rinfo = fc_iclause->rinfo;

			Assert(!fc_rinfo->pseudoconstant);
			fc_subquals = lappend(fc_subquals, fc_rinfo->clause);
			fc_subindexquals = list_concat(fc_subindexquals,
										get_actual_clauses(fc_iclause->indexquals));
			if (fc_rinfo->parent_ec)
				fc_subindexECs = lappend(fc_subindexECs, fc_rinfo->parent_ec);
		}
		/* 我们也可以添加任何索引谓词条件 */
		foreach(fc_l, fc_ipath->indexinfo->indpred)
		{
			Expr	   *fc_pred = (Expr *) lfirst(fc_l);

			
/*
			 * 我们知道索引谓词必须由查询条件整体隐含，但它可能由推送到bitmapqual的条件隐含，也可能不隐含。避免生成冗余条件。
			 */
			if (!predicate_implied_by(list_make1(fc_pred), fc_subquals, false))
			{
				fc_subquals = lappend(fc_subquals, fc_pred);
				fc_subindexquals = lappend(fc_subindexquals, fc_pred);
			}
		}
		*fc_qual = fc_subquals;
		*fc_indexqual = fc_subindexquals;
		*fc_indexECs = fc_subindexECs;
	}
	else
	{
		elog(ERROR, "unrecognized node type: %d", nodeTag(fc_bitmapqual));
		fc_plan = NULL;			/* 保持编译器安静 */
	}

	return fc_plan;
}

/*
 * create_tidscan_plan
 *	 返回一个tidscan计划，针对由'best_path'扫描的基础关系，
 *	 其限制子句为'scan_clauses'，目标列表为'tlist'。
 */
static TidScan * fc_create_tidscan_plan(PlannerInfo *fc_root, TidPath *fc_best_path,
					List *fc_tlist, List *fc_scan_clauses)
{
	TidScan    *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->path.parent->relid;
	List	   *fc_tidquals = fc_best_path->tidquals;

	/* 它应该是一个基础关系... */
	Assert(fc_scan_relid > 0);
	Assert(fc_best_path->path.parent->rtekind == RTE_RELATION);

	/*
	 * qpqual列表必须包含所有未由tidquals列表强制执行的限制条件。
	 * 由于tidquals具有OR语义，因此我们必须小心与scan_clauses匹配。
	 * 分开处理单个tidqual情况和多个tidqual情况是方便的。
	 * 在单个tidqual情况下，我们遍历scan_clauses，仍处于RestrictInfo形式时，
	 * 删除任何与tidqual冗余的条件。
	 *
	 * 在正常情况下，简单的指针相等性检查就足以发现重复的RestrictInfos，
	 * 所以我们首先尝试这个。
	 *
	 * 另一个常见情况是，scan_clauses条目是从与某些tidqual相同的EquivalenceClass生成的，
	 * 因此与其冗余，尽管不相等。
	 *
	 * 与indexpaths不同，我们不考虑predicate_implied_by()；
	 * 它可能赢得的情况数量非常少。
	 */
	if (list_length(fc_tidquals) == 1)
	{
		List	   *fc_qpqual = NIL;
		ListCell   *fc_l;

		foreach(fc_l, fc_scan_clauses)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

			if (fc_rinfo->pseudoconstant)
				continue;		/* 我们可以在这里丢掉伪常量 */
			if (list_member_ptr(fc_tidquals, fc_rinfo))
				continue;		/* 简单重复 */
			if (is_redundant_derived_clause(fc_rinfo, fc_tidquals))
				continue;		/* 来源于相同的等价类 */
			fc_qpqual = lappend(fc_qpqual, fc_rinfo);
		}
		fc_scan_clauses = fc_qpqual;
	}

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将RestrictInfo列表缩减为基本表达式；忽略伪常量 */
	fc_tidquals = extract_actual_clauses(fc_tidquals, false);
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/*
	 * 如果我们有多个tidquals，在去掉RestrictInfos后移除重复的scan_clauses更方便。
	 * 在这种情况下，由于tidquals代表OR子句，
	 * 它们不能来自EquivalenceClasses， 
	 * 因此我们不必担心匹配不相同的子句。
	 * 另一方面，由于tidpath.c将从某个OR子句中提取那些子句并构建自己的列表，
	 * 我们肯定不会有任何scan子句的指针相等性。
	 * 因此，将tidquals列表转换为显式的OR子句，看看是否可以通过equal()与任何scan子句匹配。
	 */
	if (list_length(fc_tidquals) > 1)
		fc_scan_clauses = list_difference(fc_scan_clauses,
									   list_make1(make_orclause(fc_tidquals)));

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->path.param_info)
	{
		fc_tidquals = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_tidquals);
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_tidscan(fc_tlist,
							 fc_scan_clauses,
							 fc_scan_relid,
							 fc_tidquals);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, &fc_best_path->path);

	return fc_scan_plan;
}

/*
 * create_tidrangescan_plan
 *	 返回一个tidrangescan计划，针对由'best_path'扫描的基础关系，
 *	 其限制子句为'scan_clauses'，目标列表为'tlist'。
 */
static TidRangeScan * fc_create_tidrangescan_plan(PlannerInfo *fc_root, TidRangePath *fc_best_path,
						 List *fc_tlist, List *fc_scan_clauses)
{
	TidRangeScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->path.parent->relid;
	List	   *fc_tidrangequals = fc_best_path->tidrangequals;

	/* 它应该是一个基础关系... */
	Assert(fc_scan_relid > 0);
	Assert(fc_best_path->path.parent->rtekind == RTE_RELATION);

	/*
	 * qpqual列表必须包含所有未由tidrangequals列表强制执行的限制条件。
	 * tidrangequals具有AND语义，因此我们可以简单地删除出现在其中的任何qual。
	 */
	{
		List	   *fc_qpqual = NIL;
		ListCell   *fc_l;

		foreach(fc_l, fc_scan_clauses)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_l);

			if (fc_rinfo->pseudoconstant)
				continue;		/* 我们可以在这里丢掉伪常量 */
			if (list_member_ptr(fc_tidrangequals, fc_rinfo))
				continue;		/* 简单重复 */
			fc_qpqual = lappend(fc_qpqual, fc_rinfo);
		}
		fc_scan_clauses = fc_qpqual;
	}

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将RestrictInfo列表缩减为基本表达式；忽略伪常量 */
	fc_tidrangequals = extract_actual_clauses(fc_tidrangequals, false);
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->path.param_info)
	{
		fc_tidrangequals = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_tidrangequals);
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_tidrangescan(fc_tlist,
								  fc_scan_clauses,
								  fc_scan_relid,
								  fc_tidrangequals);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, &fc_best_path->path);

	return fc_scan_plan;
}

/*
 * create_subqueryscan_plan
 *	 返回一个subqueryscan计划，针对由'best_path'扫描的基础关系，
 *	 其限制子句为'scan_clauses'，目标列表为'tlist'。
 */
static SubqueryScan * fc_create_subqueryscan_plan(PlannerInfo *fc_root, SubqueryScanPath *fc_best_path,
						 List *fc_tlist, List *fc_scan_clauses)
{
	SubqueryScan *fc_scan_plan;
	RelOptInfo *fc_rel = fc_best_path->path.parent;
	Index		fc_scan_relid = fc_rel->relid;
	Plan	   *fc_subplan;

	/* 应该是一个子查询基础关系... */
	Assert(fc_scan_relid > 0);
	Assert(fc_rel->rtekind == RTE_SUBQUERY);

	/*
	 * 递归地从Path创建Plan以用于子查询。
	 * 由于我们正在进入一个不同的规划上下文（subroot），
	 * 递归调用create_plan而不是create_plan_recurse。
	 */
	fc_subplan = create_plan(fc_rel->subroot, fc_best_path->subpath);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->path.param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
		process_subquery_nestloop_params(fc_root,
										 fc_rel->subplan_params);
	}

	fc_scan_plan = fc_make_subqueryscan(fc_tlist,
								  fc_scan_clauses,
								  fc_scan_relid,
								  fc_subplan);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, &fc_best_path->path);

	return fc_scan_plan;
}

/*
 * create_functionscan_plan
 *	 返回由 'best_path' 扫描的基本关系的 functionscan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static FunctionScan * fc_create_functionscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
						 List *fc_tlist, List *fc_scan_clauses)
{
	FunctionScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	List	   *fc_functions;

	/* 它应该是一个函数基本关系... */
	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_FUNCTION);
	fc_functions = fc_rte->functions;

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
		/* 函数表达式也可以包含嵌套循环参数 */
		fc_functions = (List *) fc_replace_nestloop_params(fc_root, (Node *) fc_functions);
	}

	fc_scan_plan = fc_make_functionscan(fc_tlist, fc_scan_clauses, fc_scan_relid,
								  fc_functions, fc_rte->funcordinality);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_tablefuncscan_plan
 *	 返回由 'best_path' 扫描的基本关系的 tablefuncscan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static TableFuncScan * fc_create_tablefuncscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
						  List *fc_tlist, List *fc_scan_clauses)
{
	TableFuncScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	TableFunc  *fc_tablefunc;

	/* 它应该是一个函数基本关系... */
	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_TABLEFUNC);
	fc_tablefunc = fc_rte->tablefunc;

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
		/* 函数表达式也可以包含嵌套循环参数 */
		fc_tablefunc = (TableFunc *) fc_replace_nestloop_params(fc_root, (Node *) fc_tablefunc);
	}

	fc_scan_plan = fc_make_tablefuncscan(fc_tlist, fc_scan_clauses, fc_scan_relid,
								   fc_tablefunc);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_valuesscan_plan
 *	 返回由 'best_path' 扫描的基本关系的 valuesscan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static ValuesScan * fc_create_valuesscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
					   List *fc_tlist, List *fc_scan_clauses)
{
	ValuesScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	List	   *fc_values_lists;

	/* 它应该是一个值基本关系... */
	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_VALUES);
	fc_values_lists = fc_rte->values_lists;

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
		/* 值列表也可以包含嵌套循环参数 */
		fc_values_lists = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_values_lists);
	}

	fc_scan_plan = fc_make_valuesscan(fc_tlist, fc_scan_clauses, fc_scan_relid,
								fc_values_lists);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_ctescan_plan
 *	 返回由 'best_path' 扫描的基本关系的 ctescan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static CteScan * fc_create_ctescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
					List *fc_tlist, List *fc_scan_clauses)
{
	CteScan    *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	SubPlan    *fc_ctesplan = NULL;
	int			fc_plan_id;
	int			fc_cte_param_id;
	PlannerInfo *fc_cteroot;
	Index		fc_levelsup;
	int			fc_ndx;
	ListCell   *fc_lc;

	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_CTE);
	Assert(!fc_rte->self_reference);

	/*
	 * 找到引用的 CTE，并定位之前为其制作的 SubPlan。
	 */
	fc_levelsup = fc_rte->ctelevelsup;
	fc_cteroot = fc_root;
	while (fc_levelsup-- > 0)
	{
		fc_cteroot = fc_cteroot->parent_root;
		if (!fc_cteroot)			/* 不应该发生 */
			elog(ERROR, "bad levelsup for CTE \"%s\"", fc_rte->ctename);
	}

	/*
	 * 注意：如果我们仍在规划 CTE（即，这是来自另一个 CTE 的侧引用），cte_plan_ids 可能会比 cteList 短。
	 * 所以我们不能在这里使用 forboth。
	 */
	fc_ndx = 0;
	foreach(fc_lc, fc_cteroot->parse->cteList)
	{
		CommonTableExpr *fc_cte = (CommonTableExpr *) lfirst(fc_lc);

		if (strcmp(fc_cte->ctename, fc_rte->ctename) == 0)
			break;
		fc_ndx++;
	}
	if (fc_lc == NULL)				/* 不应该发生 */
		elog(ERROR, "could not find CTE \"%s\"", fc_rte->ctename);
	if (fc_ndx >= list_length(fc_cteroot->cte_plan_ids))
		elog(ERROR, "could not find plan for CTE \"%s\"", fc_rte->ctename);
	fc_plan_id = list_nth_int(fc_cteroot->cte_plan_ids, fc_ndx);
	if (fc_plan_id <= 0)
		elog(ERROR, "no plan was made for CTE \"%s\"", fc_rte->ctename);
	foreach(fc_lc, fc_cteroot->init_plans)
	{
		fc_ctesplan = (SubPlan *) lfirst(fc_lc);
		if (fc_ctesplan->plan_id == fc_plan_id)
			break;
	}
	if (fc_lc == NULL)				/* 不应该发生 */
		elog(ERROR, "could not find plan for CTE \"%s\"", fc_rte->ctename);

	/*
	 * 我们需要 CTE 参数 ID，它是 SubPlan 的唯一成员
	 * setParam 列表。
	 */
	fc_cte_param_id = linitial_int(fc_ctesplan->setParam);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_ctescan(fc_tlist, fc_scan_clauses, fc_scan_relid,
							 fc_plan_id, fc_cte_param_id);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_namedtuplestorescan_plan
 *	 返回由 'best_path' 扫描的基本关系的 tuplestorescan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表
 *	 'tlist'。
 */
static NamedTuplestoreScan * fc_create_namedtuplestorescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
								List *fc_tlist, List *fc_scan_clauses)
{
	NamedTuplestoreScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;

	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_NAMEDTUPLESTORE);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_namedtuplestorescan(fc_tlist, fc_scan_clauses, fc_scan_relid,
										 fc_rte->enrname);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_resultscan_plan
 *	 返回 RTE_RESULT 基本关系的 Result 计划
 *	 由 'best_path' 扫描，具有限制子句 'scan_clauses' 和目标列表
 *	 'tlist'。
 */
static Result * fc_create_resultscan_plan(PlannerInfo *fc_root, Path *fc_best_path,
					   List *fc_tlist, List *fc_scan_clauses)
{
	Result	   *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte PG_USED_FOR_ASSERTS_ONLY;

	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_RESULT);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_result(fc_tlist, (Node *) fc_scan_clauses, NULL);

	fc_copy_generic_path_info(&fc_scan_plan->plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_worktablescan_plan
 *	 返回由 'best_path' 扫描的基本关系的 worktablescan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static WorkTableScan * fc_create_worktablescan_plan(PlannerInfo *fc_root, Path *fc_best_path,
						  List *fc_tlist, List *fc_scan_clauses)
{
	WorkTableScan *fc_scan_plan;
	Index		fc_scan_relid = fc_best_path->parent->relid;
	RangeTblEntry *fc_rte;
	Index		fc_levelsup;
	PlannerInfo *fc_cteroot;

	Assert(fc_scan_relid > 0);
	fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
	Assert(fc_rte->rtekind == RTE_CTE);
	Assert(fc_rte->self_reference);

	/*
	 * 我们需要找到工作表参数 ID，它位于处理递归 UNION 的计划级别中，
	 * 是 CTE 来源的一层 *下面*。
	 */
	fc_levelsup = fc_rte->ctelevelsup;
	if (fc_levelsup == 0)			/* 不应该发生 */
		elog(ERROR, "bad levelsup for CTE \"%s\"", fc_rte->ctename);
	fc_levelsup--;
	fc_cteroot = fc_root;
	while (fc_levelsup-- > 0)
	{
		fc_cteroot = fc_cteroot->parent_root;
		if (!fc_cteroot)			/* 不应该发生 */
			elog(ERROR, "bad levelsup for CTE \"%s\"", fc_rte->ctename);
	}
	if (fc_cteroot->wt_param_id < 0)	/* 不应该发生 */
		elog(ERROR, "could not find param ID for CTE \"%s\"", fc_rte->ctename);

	/* 将子句排序为最佳执行顺序 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/* 将 RestrictInfo 列表简化为基本表达式；忽略伪常量 */
	fc_scan_clauses = extract_actual_clauses(fc_scan_clauses, false);

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->param_info)
	{
		fc_scan_clauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_clauses);
	}

	fc_scan_plan = fc_make_worktablescan(fc_tlist, fc_scan_clauses, fc_scan_relid,
								   fc_cteroot->wt_param_id);

	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, fc_best_path);

	return fc_scan_plan;
}

/*
 * create_foreignscan_plan
 *	 返回由 'best_path' 扫描的关系的 foreignscan 计划
 *	 以及限制子句 'scan_clauses' 和目标列表 'tlist'。
 */
static ForeignScan * fc_create_foreignscan_plan(PlannerInfo *fc_root, ForeignPath *fc_best_path,
						List *fc_tlist, List *fc_scan_clauses)
{
	ForeignScan *fc_scan_plan;
	RelOptInfo *fc_rel = fc_best_path->path.parent;
	Index		fc_scan_relid = fc_rel->relid;
	Oid			fc_rel_oid = InvalidOid;
	Plan	   *fc_outer_plan = NULL;

	Assert(fc_rel->fdwroutine != NULL);

	/* 如果有子路径，进行转换 */
	if (fc_best_path->fdw_outerpath)
		fc_outer_plan = fc_create_plan_recurse(fc_root, fc_best_path->fdw_outerpath,
										 CP_EXACT_TLIST);

	/*
	 * 如果我们正在扫描基本关系，则获取其 OID。 （如果
	 * 扫描的是连接关系，则无关紧要。）
	 */
	if (fc_scan_relid > 0)
	{
		RangeTblEntry *fc_rte;

		Assert(fc_rel->rtekind == RTE_RELATION);
		fc_rte = planner_rt_fetch(fc_scan_relid, fc_root);
		Assert(fc_rte->rtekind == RTE_RELATION);
		fc_rel_oid = fc_rte->relid;
	}

	/*
	 * 将子句排序为最佳执行顺序。 我们首先这样做，因为 FDW
	 * 可能比我们拥有更多信息并希望调整排序。
	 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/*
	 * 让 FDW 在限制子句上执行其处理并生成计划节点。
	 * 注意，FDW 可能会删除打算远程执行的限制
	 * 子句，甚至添加更多（如果它
	 * 选择了某些用于远程使用的连接子句，但也希望它们
	 * 在本地重新检查）。
	 */
	fc_scan_plan = fc_rel->fdwroutine->GetForeignPlan(fc_root, fc_rel, fc_rel_oid,
												fc_best_path,
												fc_tlist, fc_scan_clauses,
												fc_outer_plan);

	/* 从 Path 复制成本数据到 Plan；无需让 FDW 这样做 */
	fc_copy_generic_path_info(&fc_scan_plan->scan.plan, &fc_best_path->path);

	/* 复制外部服务器 OID；同样，无需让 FDW 这样做 */
	fc_scan_plan->fs_server = fc_rel->serverid;

	/*
	 * 同样，复制由此外部扫描表示的 relids。 上层 rel 没有
	 * relids 集，但它覆盖所有参与底层扫描的基本关系，
	 * 因此使用根节点的 all_baserels。
	 */
	if (fc_rel->reloptkind == RELOPT_UPPER_REL)
		fc_scan_plan->fs_relids = fc_root->all_baserels;
	else
		fc_scan_plan->fs_relids = fc_best_path->path.parent->relids;

	/*
	 * 如果这是一个外部连接，并且为了使其有效地向下推送，
	 * 我们必须假设当前用户与查询中显式命名的某个用户相同，
	 * 则将已完成的计划标记为依赖于当前用户。
	 */
	if (fc_rel->useridiscurrent)
		fc_root->glob->dependsOnRole = true;

	/*
	 * 在 qual、fdw_exprs 和 fdw_recheck_quals 表达式中用嵌套循环参数替换任何外层关系变量。
	 * 我们最后这样做，以便 FDW 不必参与。 （注意，fdw_exprs 或
	 * fdw_recheck_quals 的部分可能来自连接子句，因此在
	 * scan_clauses 上提前执行此操作将不起作用。） 我们假设
	 * fdw_scan_tlist 中不包含此类变量。
	 */
	if (fc_best_path->path.param_info)
	{
		fc_scan_plan->scan.plan.qual = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_plan->scan.plan.qual);
		fc_scan_plan->fdw_exprs = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_scan_plan->fdw_exprs);
		fc_scan_plan->fdw_recheck_quals = (List *)
			fc_replace_nestloop_params(fc_root,
									(Node *) fc_scan_plan->fdw_recheck_quals);
	}

	/*
	 * 如果 rel 是基本关系，请检测是否请求了
	 * 系统列。 （如果 rel 是连接关系，rel->relid 将为
	 * 0，但在 rel 的目标列表或限制子句中不能有 relid 为 0 的 Var，
	 * 因此在这种情况下我们跳过。 注意，假设已连接的基本关系中的任何此类
	 * 列都包含在 fdw_scan_tlist 中。） 这是有点临时解决方案，可能会在某天消失，
	 * 因此我们故意将其排除在提供给 FDW 的 API 之外。
	 */
	fc_scan_plan->fsSystemCol = false;
	if (fc_scan_relid > 0)
	{
		Bitmapset  *fc_attrs_used = NULL;
		ListCell   *fc_lc;
		int			fc_i;

		/*
		 * 首先，检查连接或最终输出所需的所有属性。
		 * 注意：我们必须查看 rel 的目标列表，而不是 attr_needed 数据，
		 * 因为对于继承子关系，attr_needed 不会被计算。
		 */
		pull_varattnos((Node *) fc_rel->reltarget->exprs, fc_scan_relid, &fc_attrs_used);

		/* 添加所有在限制子句中使用的属性。 */
		foreach(fc_lc, fc_rel->baserestrictinfo)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

			pull_varattnos((Node *) fc_rinfo->clause, fc_scan_relid, &fc_attrs_used);
		}

		/* 现在，是否请求了来自 rel 的任何系统列？ */
		for (fc_i = FirstLowInvalidHeapAttributeNumber + 1; fc_i < 0; fc_i++)
		{
			if (bms_is_member(fc_i - FirstLowInvalidHeapAttributeNumber, fc_attrs_used))
			{
				fc_scan_plan->fsSystemCol = true;
				break;
			}
		}

		bms_free(fc_attrs_used);
	}

	return fc_scan_plan;
}

/*
 * create_customscan_plan
 *
 * 将 CustomPath 转换为计划。
 */
static CustomScan * fc_create_customscan_plan(PlannerInfo *fc_root, CustomPath *fc_best_path,
					   List *fc_tlist, List *fc_scan_clauses)
{
	CustomScan *fc_cplan;
	RelOptInfo *fc_rel = fc_best_path->path.parent;
	List	   *fc_custom_plans = NIL;
	ListCell   *fc_lc;

	/* 递归转换子路径。 */
	foreach(fc_lc, fc_best_path->custom_paths)
	{
		Plan	   *fc_plan = fc_create_plan_recurse(fc_root, (Path *) lfirst(fc_lc),
											   CP_EXACT_TLIST);

		fc_custom_plans = lappend(fc_custom_plans, fc_plan);
	}

	/*
	 * 将子句排序为最佳执行顺序，尽管自定义扫描提供者可以再次重新排序它们。
	 */
	fc_scan_clauses = fc_order_qual_clauses(fc_root, fc_scan_clauses);

	/*
	 * 调用自定义计划提供者创建由CustomPath表示的计划节点。
	 */
	fc_cplan = castNode(CustomScan,
					 fc_best_path->methods->PlanCustomPath(fc_root,
														fc_rel,
														fc_best_path,
														fc_tlist,
														fc_scan_clauses,
														fc_custom_plans));

	/*
	 * 将成本数据从Path复制到Plan；不需要让自定义计划提供者做这件事
	 */
	fc_copy_generic_path_info(&fc_cplan->scan.plan, &fc_best_path->path);

	/* 同样，复制由此自定义扫描表示的relids */
	fc_cplan->custom_relids = fc_best_path->path.parent->relids;

	/*
	 * 在qual和custom_exprs表达式中用nestloop参数替换任何外部关系变量。我们最后做这件事，以便自定义计划提供者不必参与。（注意，custom_exprs的部分内容可能来自连接子句，因此在scan_clauses上提前这样做是行不通的。）我们假设custom_scan_tlist不包含这样的变量。
	 */
	if (fc_best_path->path.param_info)
	{
		fc_cplan->scan.plan.qual = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_cplan->scan.plan.qual);
		fc_cplan->custom_exprs = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_cplan->custom_exprs);
	}

	return fc_cplan;
}


/*****************************************************************************
 *
 *	连接方法
 *
 *****************************************************************************/

static NestLoop * fc_create_nestloop_plan(PlannerInfo *fc_root,
					 NestPath *fc_best_path)
{
	NestLoop   *fc_join_plan;
	Plan	   *fc_outer_plan;
	Plan	   *fc_inner_plan;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->jpath.path);
	List	   *fc_joinrestrictclauses = fc_best_path->jpath.joinrestrictinfo;
	List	   *fc_joinclauses;
	List	   *fc_otherclauses;
	Relids		fc_outerrelids;
	List	   *fc_nestParams;
	Relids		fc_saveOuterRels = fc_root->curOuterRels;

	/* NestLoop可以投影，因此对子tlists不需要挑剔 */
	fc_outer_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.outerjoinpath, 0);

	/* 对于nestloop，在内部侧包括外部relids到curOuterRels中 */
	fc_root->curOuterRels = bms_union(fc_root->curOuterRels,
								   fc_best_path->jpath.outerjoinpath->parent->relids);

	fc_inner_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.innerjoinpath, 0);

	/* 恢复curOuterRels */
	bms_free(fc_root->curOuterRels);
	fc_root->curOuterRels = fc_saveOuterRels;

	/* 将连接qual子句排序为最佳执行顺序 */
	fc_joinrestrictclauses = fc_order_qual_clauses(fc_root, fc_joinrestrictclauses);

	/* 获取连接qual子句（以普通表达式形式） */
	/* 任何伪常数子句在此处被忽略 */
	if (IS_OUTER_JOIN(fc_best_path->jpath.jointype))
	{
		extract_actual_join_clauses(fc_joinrestrictclauses,
									fc_best_path->jpath.path.parent->relids,
									&fc_joinclauses, &fc_otherclauses);
	}
	else
	{
		/* 我们可以对内部连接中的所有子句一视同仁 */
		fc_joinclauses = extract_actual_clauses(fc_joinrestrictclauses, false);
		fc_otherclauses = NIL;
	}

	/* 将任何外部关系变量替换为 nestloop 参数 */
	if (fc_best_path->jpath.path.param_info)
	{
		fc_joinclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_joinclauses);
		fc_otherclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_otherclauses);
	}

	/*
	 * 确定此连接节点应提供的任何nestloop参数，并将它们从root->curOuterParams中移除。
	 */
	fc_outerrelids = fc_best_path->jpath.outerjoinpath->parent->relids;
	fc_nestParams = identify_current_nestloop_params(fc_root, fc_outerrelids);

	fc_join_plan = fc_make_nestloop(fc_tlist,
							  fc_joinclauses,
							  fc_otherclauses,
							  fc_nestParams,
							  fc_outer_plan,
							  fc_inner_plan,
							  fc_best_path->jpath.jointype,
							  fc_best_path->jpath.inner_unique);

	fc_copy_generic_path_info(&fc_join_plan->join.plan, &fc_best_path->jpath.path);

	return fc_join_plan;
}

static MergeJoin * fc_create_mergejoin_plan(PlannerInfo *fc_root,
					  MergePath *fc_best_path)
{
	MergeJoin  *fc_join_plan;
	Plan	   *fc_outer_plan;
	Plan	   *fc_inner_plan;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->jpath.path);
	List	   *fc_joinclauses;
	List	   *fc_otherclauses;
	List	   *fc_mergeclauses;
	List	   *fc_outerpathkeys;
	List	   *fc_innerpathkeys;
	int			fc_nClauses;
	Oid		   *fc_mergefamilies;
	Oid		   *fc_mergecollations;
	int		   *fc_mergestrategies;
	bool	   *fc_mergenullsfirst;
	PathKey    *fc_opathkey;
	EquivalenceClass *fc_opeclass;
	int			fc_i;
	ListCell   *fc_lc;
	ListCell   *fc_lop;
	ListCell   *fc_lip;
	Path	   *fc_outer_path = fc_best_path->jpath.outerjoinpath;
	Path	   *fc_inner_path = fc_best_path->jpath.innerjoinpath;

	/*
	 * MergeJoin可以投影，因此我们不必要求输入提供确切的tlists。然而，如果我们打算对输入的结果进行排序，最好请求一个较小的tlist，以避免排序过多不必要的数据。
	 */
	fc_outer_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.outerjoinpath,
									 (fc_best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);

	fc_inner_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.innerjoinpath,
									 (fc_best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);

	/* 将连接qual子句排序为最佳执行顺序 */
	/* 注意：不要重新排序mergeclauses */
	fc_joinclauses = fc_order_qual_clauses(fc_root, fc_best_path->jpath.joinrestrictinfo);

	/* 获取连接qual子句（以普通表达式形式） */
	/* 任何伪常数子句在此处被忽略 */
	if (IS_OUTER_JOIN(fc_best_path->jpath.jointype))
	{
		extract_actual_join_clauses(fc_joinclauses,
									fc_best_path->jpath.path.parent->relids,
									&fc_joinclauses, &fc_otherclauses);
	}
	else
	{
		/* 我们可以对内部连接中的所有子句一视同仁 */
		fc_joinclauses = extract_actual_clauses(fc_joinclauses, false);
		fc_otherclauses = NIL;
	}

	/*
	 * 从连接qual子句列表中移除mergeclauses，留下必须作为qpquals检查的qual列表。
	 */
	fc_mergeclauses = get_actual_clauses(fc_best_path->path_mergeclauses);
	fc_joinclauses = list_difference(fc_joinclauses, fc_mergeclauses);

	/*
	 * 用nestloop参数替换任何外部关系变量。mergeclauses中不应有任何变量。
	 */
	if (fc_best_path->jpath.path.param_info)
	{
		fc_joinclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_joinclauses);
		fc_otherclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_otherclauses);
	}

	/*
	 * 如有必要，重新排列mergeclauses，以便外部变量始终在左侧；使用正确的outer_is_left状态标记mergeclause restrictinfos。
	 */
	fc_mergeclauses = get_switched_clauses(fc_best_path->path_mergeclauses,
										fc_best_path->jpath.outerjoinpath->parent->relids);

	/*
	 * 如果需要，为外部和内部路径创建显式排序节点。
	 */
	if (fc_best_path->outersortkeys)
	{
		Relids		fc_outer_relids = fc_outer_path->parent->relids;
		Sort	   *fc_sort = fc_make_sort_from_pathkeys(fc_outer_plan,
												   fc_best_path->outersortkeys,
												   fc_outer_relids);

		fc_label_sort_with_costsize(fc_root, fc_sort, -1.0);
		fc_outer_plan = (Plan *) fc_sort;
		fc_outerpathkeys = fc_best_path->outersortkeys;
	}
	else
		fc_outerpathkeys = fc_best_path->jpath.outerjoinpath->pathkeys;

	if (fc_best_path->innersortkeys)
	{
		Relids		fc_inner_relids = fc_inner_path->parent->relids;
		Sort	   *fc_sort = fc_make_sort_from_pathkeys(fc_inner_plan,
												   fc_best_path->innersortkeys,
												   fc_inner_relids);

		fc_label_sort_with_costsize(fc_root, fc_sort, -1.0);
		fc_inner_plan = (Plan *) fc_sort;
		fc_innerpathkeys = fc_best_path->innersortkeys;
	}
	else
		fc_innerpathkeys = fc_best_path->jpath.innerjoinpath->pathkeys;

	/*
	 * 如果指定，添加一个materialize节点以保护内部计划免受处理mark/restore的需要。
	 */
	if (fc_best_path->materialize_inner)
	{
		Plan	   *fc_matplan = (Plan *) fc_make_material(fc_inner_plan);

		/*
		 * 我们假设materialize不会溢出到磁盘，因此每个元组仅收取cpu_operator_cost。（保持此估算与final_cost_mergejoin同步。）
		 */
		fc_copy_plan_costsize(fc_matplan, fc_inner_plan);
		fc_matplan->total_cost += cpu_operator_cost * fc_matplan->plan_rows;

		fc_inner_plan = fc_matplan;
	}

	/*
	 * 计算执行器所需的opfamily/collation/strategy/nullsfirst数组。信息在两个输入的pathkeys中，但我们需要小心mergeclauses共享pathkey的可能性，以及内部pathkeys可能未按mergeclauses匹配的顺序排列的可能性。
	 */
	fc_nClauses = list_length(fc_mergeclauses);
	Assert(fc_nClauses == list_length(fc_best_path->path_mergeclauses));
	fc_mergefamilies = (Oid *) palloc(fc_nClauses * sizeof(Oid));
	fc_mergecollations = (Oid *) palloc(fc_nClauses * sizeof(Oid));
	fc_mergestrategies = (int *) palloc(fc_nClauses * sizeof(int));
	fc_mergenullsfirst = (bool *) palloc(fc_nClauses * sizeof(bool));

	fc_opathkey = NULL;
	fc_opeclass = NULL;
	fc_lop = list_head(fc_outerpathkeys);
	fc_lip = list_head(fc_innerpathkeys);
	fc_i = 0;
	foreach(fc_lc, fc_best_path->path_mergeclauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);
		EquivalenceClass *fc_oeclass;
		EquivalenceClass *fc_ieclass;
		PathKey    *fc_ipathkey = NULL;
		EquivalenceClass *fc_ipeclass = NULL;
		bool		fc_first_inner_match = false;

		/* 从mergeclause获取外部/内部eclass */
		if (fc_rinfo->outer_is_left)
		{
			fc_oeclass = fc_rinfo->left_ec;
			fc_ieclass = fc_rinfo->right_ec;
		}
		else
		{
			fc_oeclass = fc_rinfo->right_ec;
			fc_ieclass = fc_rinfo->left_ec;
		}
		Assert(fc_oeclass != NULL);
		Assert(fc_ieclass != NULL);

		/*
		 * 我们必须通过匹配eclasses来识别与此子句相关联的pathkey元素（这应该会给出唯一匹配，因为pathkey列表应该是规范的）。在典型情况下，merge clauses与pathkeys是一对一的，但在处理部分冗余查询条件时，情况更复杂。
		 *
		 * lop和lip引用第一个尚未匹配的pathkey元素。如果它们为NULL，则表示所有pathkey元素均已匹配。
		 *
		 * 外部pathkeys的顺序应与mergeclauses匹配，按构造（参见find_mergeclauses_for_outer_pathkeys()）。对于同一外部pathkey，可能有多个mergeclause，但不会完全跳过任何pathkey。
		 */
		if (fc_oeclass != fc_opeclass)	/* 多个匹配不相关 */
		{
			/* 不匹配当前的opathkey，所以必须匹配下一个 */
			if (fc_lop == NULL)
				elog(ERROR, "outer pathkeys do not match mergeclauses");
			fc_opathkey = (PathKey *) lfirst(fc_lop);
			fc_opeclass = fc_opathkey->pk_eclass;
			fc_lop = lnext(fc_outerpathkeys, fc_lop);
			if (fc_oeclass != fc_opeclass)
				elog(ERROR, "outer pathkeys do not match mergeclauses");
		}

		
/*
		 * 内部路径键同样不应有跳过的键，但如果我们有冗余的路径键，则合并条件可能会引用某个较早的内部路径键。例如，我们可能会有像"o.a = i.x AND o.b = i.y AND o.c = i.x"这样的合并条件。隐含的内部排序为"ORDER BY x, y, x"，但路径键机制将第二次按x排序视为冗余，因此此代码必须应对这种情况。
		 *
		 * 隐含的内部关系排序也可能是"ORDER BY x, y, x DESC"。我们依然将第二个x视为冗余；但这意味着冗余的内部路径键的排序顺序不应被视为重要。因此，我们必须检测这是否是与内部路径键匹配的第一个条件。
		 */
		if (fc_lip)
		{
			fc_ipathkey = (PathKey *) lfirst(fc_lip);
			fc_ipeclass = fc_ipathkey->pk_eclass;
			if (fc_ieclass == fc_ipeclass)
			{
				/* 成功的首次匹配到此内部路径键 */
				fc_lip = lnext(fc_innerpathkeys, fc_lip);
				fc_first_inner_match = true;
			}
		}
		if (!fc_first_inner_match)
		{
			/* 冗余子句 ... 必须匹配位于 lip 之前的某些内容 */
			ListCell   *fc_l2;

			foreach(fc_l2, fc_innerpathkeys)
			{
				if (fc_l2 == fc_lip)
					break;
				fc_ipathkey = (PathKey *) lfirst(fc_l2);
				fc_ipeclass = fc_ipathkey->pk_eclass;
				if (fc_ieclass == fc_ipeclass)
					break;
			}
			if (fc_ieclass != fc_ipeclass)
				elog(ERROR, "inner pathkeys do not match mergeclauses");
		}

		/*
		 * 路径键在操作族和排序规则方面应该始终彼此匹配（这会影响相等性），
		 * 但如果我们考虑一个冗余的内部路径键，它的排序顺序可能不匹配。
		 * 在这种情况下，我们可以忽略内部路径键的排序顺序而使用外部路径键的。
		 * （实际上，我们是在向执行器谎报这个内部列的排序方向，
		 * 但这没有关系，因为在运行时行比较中，
		 * 只有在早期列包含相同的 eclass 并且有相等性时才会达到此列。
		 * 对于在早期列中具有给定值的内部行的范围，此列中可能只有一个值，
		 * 所以我们想象这个列的排序方式并无所谓。）但一个非冗余的内部路径键最好也匹配外部的排序。
		 */
		if (fc_opathkey->pk_opfamily != fc_ipathkey->pk_opfamily ||
			fc_opathkey->pk_eclass->ec_collation != fc_ipathkey->pk_eclass->ec_collation)
			elog(ERROR, "left and right pathkeys do not match in mergejoin");
		if (fc_first_inner_match &&
			(fc_opathkey->pk_strategy != fc_ipathkey->pk_strategy ||
			 fc_opathkey->pk_nulls_first != fc_ipathkey->pk_nulls_first))
			elog(ERROR, "left and right pathkeys do not match in mergejoin");

		/* 好的，保存执行器信息 */
		fc_mergefamilies[fc_i] = fc_opathkey->pk_opfamily;
		fc_mergecollations[fc_i] = fc_opathkey->pk_eclass->ec_collation;
		fc_mergestrategies[fc_i] = fc_opathkey->pk_strategy;
		fc_mergenullsfirst[fc_i] = fc_opathkey->pk_nulls_first;
		fc_i++;
	}

	/*
	 * 注意：如果我们有额外的路径键元素（即，lop 或 lip 在这里不是 NULL），这不是一个错误。
	 * 输入路径可能比我们当前的合并连接所需的排序更佳。
	 */

	/*
	 * 现在我们可以构建合并连接节点。
	 */
	fc_join_plan = fc_make_mergejoin(fc_tlist,
							   fc_joinclauses,
							   fc_otherclauses,
							   fc_mergeclauses,
							   fc_mergefamilies,
							   fc_mergecollations,
							   fc_mergestrategies,
							   fc_mergenullsfirst,
							   fc_outer_plan,
							   fc_inner_plan,
							   fc_best_path->jpath.jointype,
							   fc_best_path->jpath.inner_unique,
							   fc_best_path->skip_mark_restore);

	/* 排序和物化步骤的成本已经包含在路径成本中 */
	fc_copy_generic_path_info(&fc_join_plan->join.plan, &fc_best_path->jpath.path);

	return fc_join_plan;
}

static HashJoin * fc_create_hashjoin_plan(PlannerInfo *fc_root,
					 HashPath *fc_best_path)
{
	HashJoin   *fc_join_plan;
	Hash	   *fc_hash_plan;
	Plan	   *fc_outer_plan;
	Plan	   *fc_inner_plan;
	List	   *fc_tlist = fc_build_path_tlist(fc_root, &fc_best_path->jpath.path);
	List	   *fc_joinclauses;
	List	   *fc_otherclauses;
	List	   *fc_hashclauses;
	List	   *fc_hashoperators = NIL;
	List	   *fc_hashcollations = NIL;
	List	   *fc_inner_hashkeys = NIL;
	List	   *fc_outer_hashkeys = NIL;
	Oid			fc_skewTable = InvalidOid;
	AttrNumber	fc_skewColumn = InvalidAttrNumber;
	bool		fc_skewInherit = false;
	ListCell   *fc_lc;

	/*
	 * HashJoin 可以进行投影，因此我们不必要求输入的确切 tlists。
	 * 然而，最好从内部侧请求一个小的 tlist，以便我们不存储更多数据。
	 * 同样，如果我们预计分批，则从外部侧请求一个小的 tlist，
	 * 以便我们不在外部批处理文件中放入额外数据。
	 */
	fc_outer_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.outerjoinpath,
									 (fc_best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);

	fc_inner_plan = fc_create_plan_recurse(fc_root, fc_best_path->jpath.innerjoinpath,
									 CP_SMALL_TLIST);

	/* 将连接qual子句排序为最佳执行顺序 */
	fc_joinclauses = fc_order_qual_clauses(fc_root, fc_best_path->jpath.joinrestrictinfo);
	/* 对哈希子句进行排序是没有意义的... */

	/* 获取连接qual子句（以普通表达式形式） */
	/* 任何伪常数子句在此处被忽略 */
	if (IS_OUTER_JOIN(fc_best_path->jpath.jointype))
	{
		extract_actual_join_clauses(fc_joinclauses,
									fc_best_path->jpath.path.parent->relids,
									&fc_joinclauses, &fc_otherclauses);
	}
	else
	{
		/* 我们可以对内部连接中的所有子句一视同仁 */
		fc_joinclauses = extract_actual_clauses(fc_joinclauses, false);
		fc_otherclauses = NIL;
	}

	/*
	 * 从连接资格子句列表中删除哈希子句，留下必须检查的资格列表作为 qpquals。
	 */
	fc_hashclauses = get_actual_clauses(fc_best_path->path_hashclauses);
	fc_joinclauses = list_difference(fc_joinclauses, fc_hashclauses);

	/*
	 * 将任何外部关系变量替换为 nestloop 参数。
	 * 在哈希子句中不应该有。
	 */
	if (fc_best_path->jpath.path.param_info)
	{
		fc_joinclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_joinclauses);
		fc_otherclauses = (List *)
			fc_replace_nestloop_params(fc_root, (Node *) fc_otherclauses);
	}

	/*
	 * 如有必要，重新排列哈希子句，以便外部变量始终位于左侧。
	 */
	fc_hashclauses = get_switched_clauses(fc_best_path->path_hashclauses,
									   fc_best_path->jpath.outerjoinpath->parent->relids);

	/*
	 * 如果只有一个连接子句，并且我们能够将外部变量识别为简单列引用，
	 * 则提供其标识以便可能在偏斜优化中使用。
	 * （注意：原则上，我们可以用多个连接子句进行偏斜优化，
	 * 但我们必须能够确定外部值的最常见组合，
	 * 目前我们没有足够的统计数据来做到这一点。）
	 */
	if (list_length(fc_hashclauses) == 1)
	{
		OpExpr	   *fc_clause = (OpExpr *) linitial(fc_hashclauses);
		Node	   *fc_node;

		Assert(is_opclause(fc_clause));
		fc_node = (Node *) linitial(fc_clause->args);
		if (IsA(fc_node, RelabelType))
			fc_node = (Node *) ((RelabelType *) fc_node)->arg;
		if (IsA(fc_node, Var))
		{
			Var		   *fc_var = (Var *) fc_node;
			RangeTblEntry *fc_rte;

			fc_rte = fc_root->simple_rte_array[fc_var->varno];
			if (fc_rte->rtekind == RTE_RELATION)
			{
				fc_skewTable = fc_rte->relid;
				fc_skewColumn = fc_var->varattno;
				fc_skewInherit = fc_rte->inh;
			}
		}
	}

	/*
	 * 收集与哈希相关的信息。已对哈希表达式进行拆分为外部/内部表达式，
	 * 以便它们可以单独计算（内部表达式用于通过 Hash 构建哈希表，
	 * 外部表达式用于在哈希连接的外部计划中从哈希表查找元组）。
	 * 还收集构建哈希表所需的操作符信息。
	 */
	foreach(fc_lc, fc_hashclauses)
	{
		OpExpr	   *fc_hclause = lfirst_node(OpExpr, fc_lc);

		fc_hashoperators = lappend_oid(fc_hashoperators, fc_hclause->opno);
		fc_hashcollations = lappend_oid(fc_hashcollations, fc_hclause->inputcollid);
		fc_outer_hashkeys = lappend(fc_outer_hashkeys, linitial(fc_hclause->args));
		fc_inner_hashkeys = lappend(fc_inner_hashkeys, lsecond(fc_hclause->args));
	}

	/*
	 * 构建哈希节点和哈希连接节点。
	 */
	fc_hash_plan = fc_make_hash(fc_inner_plan,
						  fc_inner_hashkeys,
						  fc_skewTable,
						  fc_skewColumn,
						  fc_skewInherit);

	/*
	 * 将哈希节点的启动成本和总成本设置为输入计划的总成本；
	 * 这只影响 EXPLAIN 显示，而不影响决策。
	 */
	fc_copy_plan_costsize(&fc_hash_plan->plan, fc_inner_plan);
	fc_hash_plan->plan.startup_cost = fc_hash_plan->plan.total_cost;

	/*
	 * 如果支持并行，执行器还需要一个来自所有参与者的预期总行数估计，
	 * 以便可以调整共享哈希表的大小。
	 */
	if (fc_best_path->jpath.path.parallel_aware)
	{
		fc_hash_plan->plan.parallel_aware = true;
		fc_hash_plan->rows_total = fc_best_path->inner_rows_total;
	}

	fc_join_plan = fc_make_hashjoin(fc_tlist,
							  fc_joinclauses,
							  fc_otherclauses,
							  fc_hashclauses,
							  fc_hashoperators,
							  fc_hashcollations,
							  fc_outer_hashkeys,
							  fc_outer_plan,
							  (Plan *) fc_hash_plan,
							  fc_best_path->jpath.jointype,
							  fc_best_path->jpath.inner_unique);

	fc_copy_generic_path_info(&fc_join_plan->join.plan, &fc_best_path->jpath.path);

	return fc_join_plan;
}


/*****************************************************************************
 *
 *	支持例程
 *
 *****************************************************************************/

/*
 * replace_nestloop_params
 *	  在给定表达式中用 nestloop 参数替换外部关系变量和占位变量
 *
 * 所有属于由 root->curOuterRels 确定的关系的变量和占位变量被替换为参数，
 * 并且如果尚不存在，则向 root->curOuterParams 添加条目。
 */
static Node * fc_replace_nestloop_params(PlannerInfo *fc_root, Node *fc_expr)
{
	/* 不需要设置树遍历，因此我们出发吧 */
	return fc_replace_nestloop_params_mutator(fc_expr, fc_root);
}

static Node * fc_replace_nestloop_params_mutator(Node *fc_node, PlannerInfo *fc_root)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		/* 到此为止，较高层次的变量应该早已消失 */
		Assert(fc_var->varlevelsup == 0);
		/* 如果不需要替换，我们可以不修改地返回变量 */
		if (IS_SPECIAL_VARNO(fc_var->varno) ||
			!bms_is_member(fc_var->varno, fc_root->curOuterRels))
			return fc_node;
		/* 用 nestloop 参数替换变量 */
		return (Node *) replace_nestloop_param_var(fc_root, fc_var);
	}
	if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		/* 到此为止，较高层次的占位变量应该早已消失 */
		Assert(fc_phv->phlevelsup == 0);

		/*
		 * 检查我们是否需要替换 PHV。我们使用 bms_overlap 作为一个
		 * 便宜/快速的测试，以查看 PHV 是否可能在外部
		 * 关系中被评估，然后获取它的 PlaceHolderInfo 以确定。
		 */
		if (!bms_overlap(fc_phv->phrels, fc_root->curOuterRels) ||
			!bms_is_subset(find_placeholder_info(fc_root, fc_phv, false)->ph_eval_at,
						   fc_root->curOuterRels))
		{
			/*
			 * 我们不能替换整个 PHV，但我们可能仍然需要
			 * 替换其表达式中的变量或 PHV，以防它最终
			 * 实际在这里被评估。它可能在
			 * 此计划节点或某个子节点中被评估；在后者情况下，我们并不
			 * 真正需要在这里处理该表达式，但我们没有足够的信息来判断是否是这种情况。扁平复制 PHV
			 * 节点，然后在其表达式上递归。
			 *
			 * 注意，完成后，我们可能会在计划树的不同部分拥有同一
			 * PHV 内容的不同表示。这是可以的，因为 equal() 将仅在
			 * phid/phlevelsup 上匹配，因此 setrefs.c 仍将识别对
			 * 同一 PHV 的较低级别副本的上级引用。
			 */
			PlaceHolderVar *fc_newphv = makeNode(PlaceHolderVar);

			memcpy(fc_newphv, fc_phv, sizeof(PlaceHolderVar));
			fc_newphv->phexpr = (Expr *)
				fc_replace_nestloop_params_mutator((Node *) fc_phv->phexpr,
												fc_root);
			return (Node *) fc_newphv;
		}
		/* 将 PlaceHolderVar 替换为 nestloop Param */
		return (Node *) replace_nestloop_param_placeholdervar(fc_root, fc_phv);
	}
	return expression_tree_mutator(fc_node,
								   fc_replace_nestloop_params_mutator,
								   (void *) fc_root);
}

/*
 * fix_indexqual_references
 *	  调整 indexqual 子句以符合执行器的 indexqual
 *	  机制所需的形式。
 *
 * 我们在这里有三个任务：
 *	* 从输入的 IndexClause 列表中选择实际的 qual 子句，
 *	  并从 qual 子句中删除 RestrictInfo 节点。
 *	* 将任何外部关系 Var 或 PHV 节点替换为 nestloop Params。
 *	  (XXX 最终，这个责任应该移交给其他地方？)
 *	* 索引键必须由 Var 节点表示，varattno 设置为
 *	  索引的属性编号，而不是原始关系中的属性编号。
 *
 * *stripped_indexquals_p 接收实际 qual 子句的列表。
 *
 * *fixed_indexquals_p 接收调整后的 quals 列表。这是一个与原始无
 * 子结构共享的副本；在其中有子计划的情况下，这一点很重要（我们
 * 需要对子计划树的两个单独副本，否则事情会搞砸）。
 */
static void fc_fix_indexqual_references(PlannerInfo *fc_root, IndexPath *fc_index_path,
						 List **fc_stripped_indexquals_p, List **fc_fixed_indexquals_p)
{
	IndexOptInfo *fc_index = fc_index_path->indexinfo;
	List	   *fc_stripped_indexquals;
	List	   *fc_fixed_indexquals;
	ListCell   *fc_lc;

	fc_stripped_indexquals = fc_fixed_indexquals = NIL;

	foreach(fc_lc, fc_index_path->indexclauses)
	{
		IndexClause *fc_iclause = lfirst_node(IndexClause, fc_lc);
		int			fc_indexcol = fc_iclause->indexcol;
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_iclause->indexquals)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc2);
			Node	   *fc_clause = (Node *) fc_rinfo->clause;

			fc_stripped_indexquals = lappend(fc_stripped_indexquals, fc_clause);
			fc_clause = fc_fix_indexqual_clause(fc_root, fc_index, fc_indexcol,
										  fc_clause, fc_iclause->indexcols);
			fc_fixed_indexquals = lappend(fc_fixed_indexquals, fc_clause);
		}
	}

	*fc_stripped_indexquals_p = fc_stripped_indexquals;
	*fc_fixed_indexquals_p = fc_fixed_indexquals;
}

/*
 * fix_indexorderby_references
 *	  调整 indexorderby 子句以符合执行器的索引
 *	  机制所需的形式。
 *
 * 这是 fix_indexqual_references 的简化版本。输入是
 * 纯子句和一个单独的 indexcol 列表，而不是 IndexClauses。
 */
static List * fc_fix_indexorderby_references(PlannerInfo *fc_root, IndexPath *fc_index_path)
{
	IndexOptInfo *fc_index = fc_index_path->indexinfo;
	List	   *fc_fixed_indexorderbys;
	ListCell   *fc_lcc,
			   *fc_lci;

	fc_fixed_indexorderbys = NIL;

	forboth(fc_lcc, fc_index_path->indexorderbys, fc_lci, fc_index_path->indexorderbycols)
	{
		Node	   *fc_clause = (Node *) lfirst(fc_lcc);
		int			fc_indexcol = lfirst_int(fc_lci);

		fc_clause = fc_fix_indexqual_clause(fc_root, fc_index, fc_indexcol, fc_clause, NIL);
		fc_fixed_indexorderbys = lappend(fc_fixed_indexorderbys, fc_clause);
	}

	return fc_fixed_indexorderbys;
}

/*
 * fix_indexqual_clause
 *	  将单个 indexqual 子句转换为执行器所需的形式。
 *
 * 我们在这里替换 nestloop 参数，将索引键变量
 * 或表达式替换为索引 Var 节点。
 */
static Node * fc_fix_indexqual_clause(PlannerInfo *fc_root, IndexOptInfo *fc_index, int fc_indexcol,
					 Node *fc_clause, List *fc_indexcolnos)
{
	/*
	 * 用 nestloop 参数替换任何外部关系变量。
	 *
	 * 这还会复制该子句，因此在下面就可以安全地就地修改它。
	 */
	fc_clause = fc_replace_nestloop_params(fc_root, fc_clause);

	if (IsA(fc_clause, OpExpr))
	{
		OpExpr	   *fc_op = (OpExpr *) fc_clause;

		/* 用索引变量替换索引键表达式。 */
		linitial(fc_op->args) = fc_fix_indexqual_operand(linitial(fc_op->args),
												   fc_index,
												   fc_indexcol);
	}
	else if (IsA(fc_clause, RowCompareExpr))
	{
		RowCompareExpr *fc_rc = (RowCompareExpr *) fc_clause;
		ListCell   *fc_lca,
				   *fc_lcai;

		/* 用索引变量替换索引键表达式。 */
		Assert(list_length(fc_rc->largs) == list_length(fc_indexcolnos));
		forboth(fc_lca, fc_rc->largs, fc_lcai, fc_indexcolnos)
		{
			lfirst(fc_lca) = fc_fix_indexqual_operand(lfirst(fc_lca),
												fc_index,
												lfirst_int(fc_lcai));
		}
	}
	else if (IsA(fc_clause, ScalarArrayOpExpr))
	{
		ScalarArrayOpExpr *fc_saop = (ScalarArrayOpExpr *) fc_clause;

		/* 用索引变量替换索引键表达式。 */
		linitial(fc_saop->args) = fc_fix_indexqual_operand(linitial(fc_saop->args),
													 fc_index,
													 fc_indexcol);
	}
	else if (IsA(fc_clause, NullTest))
	{
		NullTest   *fc_nt = (NullTest *) fc_clause;

		/* 用索引变量替换索引键表达式。 */
		fc_nt->arg = (Expr *) fc_fix_indexqual_operand((Node *) fc_nt->arg,
												 fc_index,
												 fc_indexcol);
	}
	else
		elog(ERROR, "unsupported indexqual type: %d",
			 (int) nodeTag(fc_clause));

	return fc_clause;
}

/*
 * fix_indexqual_operand
 *	  将索引条件表达式转换为引用索引列的变量。
 *
 * 我们通过 varno == INDEX_VAR 且 varattno 等于索引的属性编号（索引列位置）的 Var 节点来表示索引键。
 *
 * 这里的大部分代码只是用来确保给定的表达式实际上与声称的索引列匹配。
 */
static Node * fc_fix_indexqual_operand(Node *fc_node, IndexOptInfo *fc_index, int fc_indexcol)
{
	Var		   *fc_result;
	int			fc_pos;
	ListCell   *fc_indexpr_item;

	/*
	 * 移除索引键的任何二进制兼容重标记
	 */
	if (IsA(fc_node, RelabelType))
		fc_node = (Node *) ((RelabelType *) fc_node)->arg;

	Assert(fc_indexcol >= 0 && fc_indexcol < fc_index->ncolumns);

	if (fc_index->indexkeys[fc_indexcol] != 0)
	{
		/* 这是一个简单的索引列 */
		if (IsA(fc_node, Var) &&
			((Var *) fc_node)->varno == fc_index->rel->relid &&
			((Var *) fc_node)->varattno == fc_index->indexkeys[fc_indexcol])
		{
			fc_result = (Var *) copyObject(fc_node);
			fc_result->varno = INDEX_VAR;
			fc_result->varattno = fc_indexcol + 1;
			return (Node *) fc_result;
		}
		else
			elog(ERROR, "index key does not match expected index column");
	}

	/* 这是一个索引表达式，因此查找并交叉检查表达式 */
	fc_indexpr_item = list_head(fc_index->indexprs);
	for (fc_pos = 0; fc_pos < fc_index->ncolumns; fc_pos++)
	{
		if (fc_index->indexkeys[fc_pos] == 0)
		{
			if (fc_indexpr_item == NULL)
				elog(ERROR, "too few entries in indexprs list");
			if (fc_pos == fc_indexcol)
			{
				Node	   *fc_indexkey;

				fc_indexkey = (Node *) lfirst(fc_indexpr_item);
				if (fc_indexkey && IsA(fc_indexkey, RelabelType))
					fc_indexkey = (Node *) ((RelabelType *) fc_indexkey)->arg;
				if (equal(fc_node, fc_indexkey))
				{
					fc_result = makeVar(INDEX_VAR, fc_indexcol + 1,
									 exprType(lfirst(fc_indexpr_item)), -1,
									 exprCollation(lfirst(fc_indexpr_item)),
									 0);
					return (Node *) fc_result;
				}
				else
					elog(ERROR, "index key does not match expected index column");
			}
			fc_indexpr_item = lnext(fc_index->indexprs, fc_indexpr_item);
		}
	}

	/* Oops... */
	elog(ERROR, "index key does not match expected index column");
	return NULL;				/* 保持编译器安静 */
}

/*
 * get_switched_clauses
 *	  给定一组合并或哈希连接条件（作为 RestrictInfo 节点），
 *	  提取裸条件，并在条件内重新排列元素（如果需要），
 *	  使外连接变量在左侧，内连接变量在右侧。
 *	  原始条件数据结构不受影响；将返回修改后的列表。
 *	  然而，我们会在每个 RestrictInfo 中设置 transient
 *	  outer_is_left 字段以显示哪个侧是哪一侧。
 */
static List * get_switched_clauses(List *fc_clauses, Relids fc_outerrelids)
{
	List	   *fc_t_list = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_clauses)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) lfirst(fc_l);
		OpExpr	   *fc_clause = (OpExpr *) fc_restrictinfo->clause;

		Assert(is_opclause(fc_clause));
		if (bms_is_subset(fc_restrictinfo->right_relids, fc_outerrelids))
		{
			/*
			 * 复制足够的结构，以便在不改变原始列表的情况下交换
			 * 条件。可以使用 copyObject，但完全深拷贝是多余的。
			 */
			OpExpr	   *fc_temp = makeNode(OpExpr);

			fc_temp->opno = fc_clause->opno;
			fc_temp->opfuncid = InvalidOid;
			fc_temp->opresulttype = fc_clause->opresulttype;
			fc_temp->opretset = fc_clause->opretset;
			fc_temp->opcollid = fc_clause->opcollid;
			fc_temp->inputcollid = fc_clause->inputcollid;
			fc_temp->args = list_copy(fc_clause->args);
			fc_temp->location = fc_clause->location;
			/* 进行交换 --- 注意这会就地修改临时节点。 */
			CommuteOpExpr(fc_temp);
			fc_t_list = lappend(fc_t_list, fc_temp);
			fc_restrictinfo->outer_is_left = false;
		}
		else
		{
			Assert(bms_is_subset(fc_restrictinfo->left_relids, fc_outerrelids));
			fc_t_list = lappend(fc_t_list, fc_clause);
			fc_restrictinfo->outer_is_left = true;
		}
	}
	return fc_t_list;
}

/*
 * order_qual_clauses
 *		给定一组将在同一计划节点上评估的条件，按照我们希望
 *		在运行时检查条件的顺序对列表进行排序。
 *
 * 当在查询中使用安全屏障条件时，列表中可能会有不同安全级别的条件。
 * 安全级别较低的条件必须在安全级别较高的条件之前，
 * 除非我们可以允许例外，将泄漏保护的条件上移。
 * 当安全级别不强制决策时，我们倾向于按估计的
 * 执行成本进行排序，从最低到最高。
 *
 * 理想情况下，顺序应该受到执行成本和选择性的组合驱动，
 * 但目前尚不清楚如何同时考虑这两者，并且考虑到估计值的不确定性
 * 决策的可靠性也会受到怀疑。因此我们只是按安全级别排序，
 * 然后按每元组的估计成本进行排序，并注意在（通常情况下）
 * 估计值相同时不改变顺序。
 *
 * 尽管这可以在裸条件或 RestrictInfos 上工作，但在
 * RestrictInfos 上应用速度更快，因为它可以重用
 * 缓存在 RestrictInfos 中的成本信息。XXX 在裸条件的
 * 情况下，我们也无法应用安全考虑。这目前没关系，
 * 因为裸条件的情况不会发生在可能存在屏障条件的地方，
 * 但最好还是将其去除。
 *
 * 注意：一些调用者传递包含稍后将被移除条目的列表；
 * 这是让这个例程看到 RestrictInfos 而不是裸条件的最简单方法。
 * 这是另一个原因，为什么在排序中考虑选择性可能会导致错误的结果。
 */
static List * fc_order_qual_clauses(PlannerInfo *fc_root, List *fc_clauses)
{
	typedef struct
	{
		Node	   *clause;
		Cost		cost;
		Index		security_level;
	} QualItem;
	int			fc_nitems = list_length(fc_clauses);
	QualItem   *fc_items;
	ListCell   *fc_lc;
	int			fc_i;
	List	   *fc_result;

	/* 对于 0 或 1 条件，不需要费力处理 */
	if (fc_nitems <= 1)
		return fc_clauses;

	/*
	 * 将项目和成本收集到一个数组中。这是为了避免在输入不是
	 * RestrictInfos 的情况下重复进行 cost_qual_eval 工作。
	 */
	fc_items = (QualItem *) palloc(fc_nitems * sizeof(QualItem));
	fc_i = 0;
	foreach(fc_lc, fc_clauses)
	{
		Node	   *fc_clause = (Node *) lfirst(fc_lc);
		QualCost	fc_qcost;

		cost_qual_eval_node(&fc_qcost, fc_clause, fc_root);
		fc_items[fc_i].clause = fc_clause;
		fc_items[fc_i].cost = fc_qcost.per_tuple;
		if (IsA(fc_clause, RestrictInfo))
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) fc_clause;

			/*
			 * 如果条件是防泄漏的，它就不必受到其名义安全级别的约束。
			 * 如果它也相对便宜（在此定义为 10 倍的 cpu_operator_cost），
			 * 假装它的安全级别为 0，这将允许它在
			 * 更高安全级别的更昂贵的条件之前。
			 * 当然，这也会强迫它在自己安全级别的更便宜的条件之前，
			 * 这并不是那么好，但我们可以通过应用成本限制来缓解
			 * 这种风险。
			 */
			if (fc_rinfo->leakproof && fc_items[fc_i].cost < 10 * cpu_operator_cost)
				fc_items[fc_i].security_level = 0;
			else
				fc_items[fc_i].security_level = fc_rinfo->security_level;
		}
		else
			fc_items[fc_i].security_level = 0;
		fc_i++;
	}

	/*
	 * 排序。我们不使用 qsort()，因为对于相等的键它没有保证稳定性。
	 * 预期条目的数量足够小，因此简单的插入排序应该足够。
	 */
	for (fc_i = 1; fc_i < fc_nitems; fc_i++)
	{
		QualItem	fc_newitem = fc_items[fc_i];
		int			fc_j;

		/* 将新项目插入已经排序的子数组中 */
		for (fc_j = fc_i; fc_j > 0; fc_j--)
		{
			QualItem   *fc_olditem = &fc_items[fc_j - 1];

			if (fc_newitem.security_level > fc_olditem->security_level ||
				(fc_newitem.security_level == fc_olditem->security_level &&
				 fc_newitem.cost >= fc_olditem->cost))
				break;
			fc_items[fc_j] = *fc_olditem;
		}
		fc_items[fc_j] = fc_newitem;
	}

	/* 转换回列表 */
	fc_result = NIL;
	for (fc_i = 0; fc_i < fc_nitems; fc_i++)
		fc_result = lappend(fc_result, fc_items[fc_i].clause);

	return fc_result;
}

/*
 * 从 Path 节点复制成本和大小信息到从中创建的 Plan 节点。
 * 执行器通常不会使用这些信息，但它需要在 EXPLAIN 中。
 * 还要复制与并行相关的标志，执行器会使用这些标志。
 */
static void fc_copy_generic_path_info(Plan *fc_dest, Path *fc_src)
{
	fc_dest->startup_cost = fc_src->startup_cost;
	fc_dest->total_cost = fc_src->total_cost;
	fc_dest->plan_rows = fc_src->rows;
	fc_dest->plan_width = fc_src->pathtarget->width;
	fc_dest->parallel_aware = fc_src->parallel_aware;
	fc_dest->parallel_safe = fc_src->parallel_safe;
}

/*
 * 从低级计划节点复制成本和大小信息到插入的节点。
 * （大多数调用者在复制后会更改信息。）
 */
static void fc_copy_plan_costsize(Plan *fc_dest, Plan *fc_src)
{
	fc_dest->startup_cost = fc_src->startup_cost;
	fc_dest->total_cost = fc_src->total_cost;
	fc_dest->plan_rows = fc_src->plan_rows;
	fc_dest->plan_width = fc_src->plan_width;
	/* 假设插入的节点不是并行感知的。 */
	fc_dest->parallel_aware = false;
	/* 如果子计划是并行安全的，则假设插入的节点也是并行安全的。 */
	fc_dest->parallel_safe = fc_src->parallel_safe;
}


/*****
 * 文件中的某些地方构建没有直接对应的 Path 节点的 Sort 节点。
 * 排序的成本应包括在我们正在处理的 Path 节点的成本中，但由于没有拆分出来，
 * 我们必须使用 cost_sort() 重新计算它。这只是为了在 EXPLAIN 中很好地标记 Sort 节点。
 *
 * limit_tuples 和 cost_sort 一样（特别是，如果没有限制，请传递 -1）
 */
static void fc_label_sort_with_costsize(PlannerInfo *fc_root, Sort *fc_plan, double fc_limit_tuples)
{
	Plan	   *fc_lefttree = fc_plan->plan.lefttree;
	Path		sort_path;		/* cost_sort 结果的占位符 */

	/*
	 * 这个函数不应该处理 IncrementalSort 计划，因为
	 * 它们仅仅从对应的 Path 节点创建。
	 */
	Assert(IsA(fc_plan, Sort));

	cost_sort(&sort_path, fc_root, NIL,
			  fc_lefttree->total_cost,
			  fc_lefttree->plan_rows,
			  fc_lefttree->plan_width,
			  0.0,
			  work_mem,
			  fc_limit_tuples);
	fc_plan->plan.startup_cost = sort_path.startup_cost;
	fc_plan->plan.total_cost = sort_path.total_cost;
	fc_plan->plan.plan_rows = fc_lefttree->plan_rows;
	fc_plan->plan.plan_width = fc_lefttree->plan_width;
	fc_plan->plan.parallel_aware = false;
	fc_plan->plan.parallel_safe = fc_lefttree->parallel_safe;
}

/*
 * bitmap_subplan_mark_shared
 *	 在位图子计划中设置 isshared 标志，以便它将在
 *	 共享内存中创建。
 */
static void fc_bitmap_subplan_mark_shared(Plan *fc_plan)
{
	if (IsA(fc_plan, BitmapAnd))
		fc_bitmap_subplan_mark_shared(linitial(((BitmapAnd *) fc_plan)->bitmapplans));
	else if (IsA(fc_plan, BitmapOr))
	{
		((BitmapOr *) fc_plan)->isshared = true;
		fc_bitmap_subplan_mark_shared(linitial(((BitmapOr *) fc_plan)->bitmapplans));
	}
	else if (IsA(fc_plan, BitmapIndexScan))
		((BitmapIndexScan *) fc_plan)->isshared = true;
	else
		elog(ERROR, "unrecognized node type: %d", nodeTag(fc_plan));
}

/*****************************************************************************
 *
 *	计划节点构建例程
 *
 * 通常，这些函数不会传入原始的 Path，因此
 * 由调用者填写 Path 中的成本/宽度字段，
 * 通常通过调用 copy_generic_path_info()。这种约定
 * 在历史上有些原因，但它确实支持一些地方，
 * 在没有完全对应的 Path 节点的情况下构建计划节点。
 * 在任何情况下，这些函数都不应该进行自己的成本计算，
 * 因为那样将与构建 Paths 时的计算重复。
 *
 *****************************************************************************/

static SeqScan * fc_make_seqscan(List *fc_qptlist,
			 List *fc_qpqual,
			 Index fc_scanrelid)
{
	SeqScan    *fc_node = makeNode(SeqScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;

	return fc_node;
}

static SampleScan * fc_make_samplescan(List *fc_qptlist,
				List *fc_qpqual,
				Index fc_scanrelid,
				TableSampleClause *fc_tsc)
{
	SampleScan *fc_node = makeNode(SampleScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->tablesample = fc_tsc;

	return fc_node;
}

static IndexScan * fc_make_indexscan(List *fc_qptlist,
			   List *fc_qpqual,
			   Index fc_scanrelid,
			   Oid fc_indexid,
			   List *fc_indexqual,
			   List *fc_indexqualorig,
			   List *fc_indexorderby,
			   List *fc_indexorderbyorig,
			   List *fc_indexorderbyops,
			   ScanDirection fc_indexscandir)
{
	IndexScan  *fc_node = makeNode(IndexScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->indexid = fc_indexid;
	fc_node->indexqual = fc_indexqual;
	fc_node->indexqualorig = fc_indexqualorig;
	fc_node->indexorderby = fc_indexorderby;
	fc_node->indexorderbyorig = fc_indexorderbyorig;
	fc_node->indexorderbyops = fc_indexorderbyops;
	fc_node->indexorderdir = fc_indexscandir;

	return fc_node;
}

static IndexOnlyScan * fc_make_indexonlyscan(List *fc_qptlist,
				   List *fc_qpqual,
				   Index fc_scanrelid,
				   Oid fc_indexid,
				   List *fc_indexqual,
				   List *fc_recheckqual,
				   List *fc_indexorderby,
				   List *fc_indextlist,
				   ScanDirection fc_indexscandir)
{
	IndexOnlyScan *fc_node = makeNode(IndexOnlyScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->indexid = fc_indexid;
	fc_node->indexqual = fc_indexqual;
	fc_node->recheckqual = fc_recheckqual;
	fc_node->indexorderby = fc_indexorderby;
	fc_node->indextlist = fc_indextlist;
	fc_node->indexorderdir = fc_indexscandir;

	return fc_node;
}

static BitmapIndexScan * fc_make_bitmap_indexscan(Index fc_scanrelid,
					  Oid fc_indexid,
					  List *fc_indexqual,
					  List *fc_indexqualorig)
{
	BitmapIndexScan *fc_node = makeNode(BitmapIndexScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = NIL;		/* 未使用 */
	fc_plan->qual = NIL;			/* 未使用 */
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->indexid = fc_indexid;
	fc_node->indexqual = fc_indexqual;
	fc_node->indexqualorig = fc_indexqualorig;

	return fc_node;
}

static BitmapHeapScan * fc_make_bitmap_heapscan(List *fc_qptlist,
					 List *fc_qpqual,
					 Plan *fc_lefttree,
					 List *fc_bitmapqualorig,
					 Index fc_scanrelid)
{
	BitmapHeapScan *fc_node = makeNode(BitmapHeapScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->bitmapqualorig = fc_bitmapqualorig;

	return fc_node;
}

static TidScan * fc_make_tidscan(List *fc_qptlist,
			 List *fc_qpqual,
			 Index fc_scanrelid,
			 List *fc_tidquals)
{
	TidScan    *fc_node = makeNode(TidScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->tidquals = fc_tidquals;

	return fc_node;
}

static TidRangeScan * fc_make_tidrangescan(List *fc_qptlist,
				  List *fc_qpqual,
				  Index fc_scanrelid,
				  List *fc_tidrangequals)
{
	TidRangeScan *fc_node = makeNode(TidRangeScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->tidrangequals = fc_tidrangequals;

	return fc_node;
}

static SubqueryScan * fc_make_subqueryscan(List *fc_qptlist,
				  List *fc_qpqual,
				  Index fc_scanrelid,
				  Plan *fc_subplan)
{
	SubqueryScan *fc_node = makeNode(SubqueryScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->subplan = fc_subplan;
	fc_node->scanstatus = SUBQUERY_SCAN_UNKNOWN;

	return fc_node;
}

static FunctionScan * fc_make_functionscan(List *fc_qptlist,
				  List *fc_qpqual,
				  Index fc_scanrelid,
				  List *fc_functions,
				  bool fc_funcordinality)
{
	FunctionScan *fc_node = makeNode(FunctionScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->functions = fc_functions;
	fc_node->funcordinality = fc_funcordinality;

	return fc_node;
}

static TableFuncScan * fc_make_tablefuncscan(List *fc_qptlist,
				   List *fc_qpqual,
				   Index fc_scanrelid,
				   TableFunc *fc_tablefunc)
{
	TableFuncScan *fc_node = makeNode(TableFuncScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->tablefunc = fc_tablefunc;

	return fc_node;
}

static ValuesScan * fc_make_valuesscan(List *fc_qptlist,
				List *fc_qpqual,
				Index fc_scanrelid,
				List *fc_values_lists)
{
	ValuesScan *fc_node = makeNode(ValuesScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->values_lists = fc_values_lists;

	return fc_node;
}

static CteScan * fc_make_ctescan(List *fc_qptlist,
			 List *fc_qpqual,
			 Index fc_scanrelid,
			 int fc_ctePlanId,
			 int fc_cteParam)
{
	CteScan    *fc_node = makeNode(CteScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->ctePlanId = fc_ctePlanId;
	fc_node->cteParam = fc_cteParam;

	return fc_node;
}

static NamedTuplestoreScan * fc_make_namedtuplestorescan(List *fc_qptlist,
						 List *fc_qpqual,
						 Index fc_scanrelid,
						 char *fc_enrname)
{
	NamedTuplestoreScan *fc_node = makeNode(NamedTuplestoreScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	/* 成本应由调用者插入 */
	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->enrname = fc_enrname;

	return fc_node;
}

static WorkTableScan * fc_make_worktablescan(List *fc_qptlist,
				   List *fc_qpqual,
				   Index fc_scanrelid,
				   int fc_wtParam)
{
	WorkTableScan *fc_node = makeNode(WorkTableScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;
	fc_node->wtParam = fc_wtParam;

	return fc_node;
}

ForeignScan *
make_foreignscan(List *fc_qptlist,
				 List *fc_qpqual,
				 Index fc_scanrelid,
				 List *fc_fdw_exprs,
				 List *fc_fdw_private,
				 List *fc_fdw_scan_tlist,
				 List *fc_fdw_recheck_quals,
				 Plan *fc_outer_plan)
{
	ForeignScan *fc_node = makeNode(ForeignScan);
	Plan	   *fc_plan = &fc_node->scan.plan;

	/* 成本将由 create_foreignscan_plan 填充 */
	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = fc_outer_plan;
	fc_plan->righttree = NULL;
	fc_node->scan.scanrelid = fc_scanrelid;

	/* 这些可能会被 FDW 的 PlanDirectModify 回调覆盖。 */
	fc_node->operation = CMD_SELECT;
	fc_node->resultRelation = 0;

	/* fs_server 将由 create_foreignscan_plan 填充 */
	fc_node->fs_server = InvalidOid;
	fc_node->fdw_exprs = fc_fdw_exprs;
	fc_node->fdw_private = fc_fdw_private;
	fc_node->fdw_scan_tlist = fc_fdw_scan_tlist;
	fc_node->fdw_recheck_quals = fc_fdw_recheck_quals;
	/* fs_relids 将由 create_foreignscan_plan 填充 */
	fc_node->fs_relids = NULL;
	/* fsSystemCol 将由 create_foreignscan_plan 填充 */
	fc_node->fsSystemCol = false;

	return fc_node;
}

static RecursiveUnion * fc_make_recursive_union(List *fc_tlist,
					 Plan *fc_lefttree,
					 Plan *fc_righttree,
					 int fc_wtParam,
					 List *fc_distinctList,
					 long fc_numGroups)
{
	RecursiveUnion *fc_node = makeNode(RecursiveUnion);
	Plan	   *fc_plan = &fc_node->plan;
	int			fc_numCols = list_length(fc_distinctList);

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = fc_righttree;
	fc_node->wtParam = fc_wtParam;

	/*
	 * 将 SortGroupClause 列表转换为属性索引和相等操作符的数组，如执行器所需
	 */
	fc_node->numCols = fc_numCols;
	if (fc_numCols > 0)
	{
		int			fc_keyno = 0;
		AttrNumber *fc_dupColIdx;
		Oid		   *fc_dupOperators;
		Oid		   *fc_dupCollations;
		ListCell   *fc_slitem;

		fc_dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numCols);
		fc_dupOperators = (Oid *) palloc(sizeof(Oid) * fc_numCols);
		fc_dupCollations = (Oid *) palloc(sizeof(Oid) * fc_numCols);

		foreach(fc_slitem, fc_distinctList)
		{
			SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_slitem);
			TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl,
													   fc_plan->targetlist);

			fc_dupColIdx[fc_keyno] = fc_tle->resno;
			fc_dupOperators[fc_keyno] = fc_sortcl->eqop;
			fc_dupCollations[fc_keyno] = exprCollation((Node *) fc_tle->expr);
			Assert(OidIsValid(fc_dupOperators[fc_keyno]));
			fc_keyno++;
		}
		fc_node->dupColIdx = fc_dupColIdx;
		fc_node->dupOperators = fc_dupOperators;
		fc_node->dupCollations = fc_dupCollations;
	}
	fc_node->numGroups = fc_numGroups;

	return fc_node;
}

static BitmapAnd * fc_make_bitmap_and(List *fc_bitmapplans)
{
	BitmapAnd  *fc_node = makeNode(BitmapAnd);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = NIL;
	fc_plan->qual = NIL;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->bitmapplans = fc_bitmapplans;

	return fc_node;
}

static BitmapOr * fc_make_bitmap_or(List *fc_bitmapplans)
{
	BitmapOr   *fc_node = makeNode(BitmapOr);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = NIL;
	fc_plan->qual = NIL;
	fc_plan->lefttree = NULL;
	fc_plan->righttree = NULL;
	fc_node->bitmapplans = fc_bitmapplans;

	return fc_node;
}

static NestLoop * fc_make_nestloop(List *fc_tlist,
			  List *fc_joinclauses,
			  List *fc_otherclauses,
			  List *fc_nestParams,
			  Plan *fc_lefttree,
			  Plan *fc_righttree,
			  JoinType fc_jointype,
			  bool fc_inner_unique)
{
	NestLoop   *fc_node = makeNode(NestLoop);
	Plan	   *fc_plan = &fc_node->join.plan;

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = fc_otherclauses;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = fc_righttree;
	fc_node->join.jointype = fc_jointype;
	fc_node->join.inner_unique = fc_inner_unique;
	fc_node->join.joinqual = fc_joinclauses;
	fc_node->nestParams = fc_nestParams;

	return fc_node;
}

static HashJoin * fc_make_hashjoin(List *fc_tlist,
			  List *fc_joinclauses,
			  List *fc_otherclauses,
			  List *fc_hashclauses,
			  List *fc_hashoperators,
			  List *fc_hashcollations,
			  List *fc_hashkeys,
			  Plan *fc_lefttree,
			  Plan *fc_righttree,
			  JoinType fc_jointype,
			  bool fc_inner_unique)
{
	HashJoin   *fc_node = makeNode(HashJoin);
	Plan	   *fc_plan = &fc_node->join.plan;

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = fc_otherclauses;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = fc_righttree;
	fc_node->hashclauses = fc_hashclauses;
	fc_node->hashoperators = fc_hashoperators;
	fc_node->hashcollations = fc_hashcollations;
	fc_node->hashkeys = fc_hashkeys;
	fc_node->join.jointype = fc_jointype;
	fc_node->join.inner_unique = fc_inner_unique;
	fc_node->join.joinqual = fc_joinclauses;

	return fc_node;
}

static Hash * fc_make_hash(Plan *fc_lefttree,
		  List *fc_hashkeys,
		  Oid fc_skewTable,
		  AttrNumber fc_skewColumn,
		  bool fc_skewInherit)
{
	Hash	   *fc_node = makeNode(Hash);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	fc_node->hashkeys = fc_hashkeys;
	fc_node->skewTable = fc_skewTable;
	fc_node->skewColumn = fc_skewColumn;
	fc_node->skewInherit = fc_skewInherit;

	return fc_node;
}

static MergeJoin * fc_make_mergejoin(List *fc_tlist,
			   List *fc_joinclauses,
			   List *fc_otherclauses,
			   List *fc_mergeclauses,
			   Oid *fc_mergefamilies,
			   Oid *fc_mergecollations,
			   int *fc_mergestrategies,
			   bool *fc_mergenullsfirst,
			   Plan *fc_lefttree,
			   Plan *fc_righttree,
			   JoinType fc_jointype,
			   bool fc_inner_unique,
			   bool fc_skip_mark_restore)
{
	MergeJoin  *fc_node = makeNode(MergeJoin);
	Plan	   *fc_plan = &fc_node->join.plan;

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = fc_otherclauses;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = fc_righttree;
	fc_node->skip_mark_restore = fc_skip_mark_restore;
	fc_node->mergeclauses = fc_mergeclauses;
	fc_node->mergeFamilies = fc_mergefamilies;
	fc_node->mergeCollations = fc_mergecollations;
	fc_node->mergeStrategies = fc_mergestrategies;
	fc_node->mergeNullsFirst = fc_mergenullsfirst;
	fc_node->join.jointype = fc_jointype;
	fc_node->join.inner_unique = fc_inner_unique;
	fc_node->join.joinqual = fc_joinclauses;

	return fc_node;
}

/*
 * make_sort --- 构建 Sort 计划节点的基本例程
 *
 * 调用者必须已经构建了 sortColIdx、sortOperators、collations 和
 * nullsFirst 数组。
 */
static Sort * fc_make_sort(Plan *fc_lefttree, int fc_numCols,
		  AttrNumber *fc_sortColIdx, Oid *fc_sortOperators,
		  Oid *fc_collations, bool *fc_nullsFirst)
{
	Sort	   *fc_node;
	Plan	   *fc_plan;

	fc_node = makeNode(Sort);

	fc_plan = &fc_node->plan;
	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;
	fc_node->numCols = fc_numCols;
	fc_node->sortColIdx = fc_sortColIdx;
	fc_node->sortOperators = fc_sortOperators;
	fc_node->collations = fc_collations;
	fc_node->nullsFirst = fc_nullsFirst;

	return fc_node;
}

/*
 * make_incrementalsort --- 构建 IncrementalSort 计划节点的基本例程
 *
 * 调用者必须已经构建了 sortColIdx、sortOperators、collations 和
 * nullsFirst 数组。
 */
static IncrementalSort * fc_make_incrementalsort(Plan *fc_lefttree, int fc_numCols, int fc_nPresortedCols,
					 AttrNumber *fc_sortColIdx, Oid *fc_sortOperators,
					 Oid *fc_collations, bool *fc_nullsFirst)
{
	IncrementalSort *fc_node;
	Plan	   *fc_plan;

	fc_node = makeNode(IncrementalSort);

	fc_plan = &fc_node->sort.plan;
	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;
	fc_node->nPresortedCols = fc_nPresortedCols;
	fc_node->sort.numCols = fc_numCols;
	fc_node->sort.sortColIdx = fc_sortColIdx;
	fc_node->sort.sortOperators = fc_sortOperators;
	fc_node->sort.collations = fc_collations;
	fc_node->sort.nullsFirst = fc_nullsFirst;

	return fc_node;
}

/*
 * prepare_sort_from_pathkeys
 *	  根据给定的 pathkeys 准备排序
 *
 * 这用于为 Sort、MergeAppend 和 Gather Merge 节点准备。它
 * 计算执行器对排序键信息的表示，并在必要时调整计划目标列表以添加 resjunk 排序列。
 *
 * 输入参数：
 *	  'lefttree' 是生成输入元组的计划节点
 *	  'pathkeys' 是结果按其排序的路径键列表
 *	  'relids' 识别正在排序的子关系（如果有的话）
 *	  'reqColIdx' 为 NULL 或所需的排序键列号数组
 *	  'adjust_tlist_in_place' 为 true 则表示 lefttree 必须被原地修改
 *
 * 我们必须将 pathkey 信息转换为排序键列号、排序操作符 OID、排序规则 OID 和 nulls-first 标志的数组，
 * 这就是执行器所需要的表示。它们返回到输出参数 *p_numsortkeys 等中。
 *
 * 在查找与等价类成员的匹配时，我们只会考虑属于给定 'relids' 的子 EC 成员。
 * 这可以防止可能的错误匹配到不包含 Vars 的子表达式。
 *
 * 如果 reqColIdx 不为 NULL，则它包含我们应该匹配的排序键列号。
 * 这在为 MergeAppend 创建子计划时使用；如果我们不能匹配列则会出现错误。
 *
 * 如果 pathkeys 包含不是简单 Vars 的表达式，我们通常需要向输入计划的目标列表中添加 resjunk 项以
 * 计算这些表达式，因为 Sort 或 MergeAppend 节点自身不会执行任何此类计算。
 * 如果输入计划类型不是可以进行投影的类型，这意味着仅为进行投影添加结果节点。
 * 但是，调用者可以传递 adjust_tlist_in_place = true 来强制 lefttree tlist 原地修改，而不管节点类型是否
 * 可以投影——我们使用它来修复 MergeAppend 自身的 tlist。
 *
 * 返回将作为 Sort 输入的节点（要么是 lefttree，或者栈叠在 lefttree 上的 Result）。
 */
static Plan * fc_prepare_sort_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys,
						   Relids fc_relids,
						   const AttrNumber *fc_reqColIdx,
						   bool fc_adjust_tlist_in_place,
						   int *fc_p_numsortkeys,
						   AttrNumber **fc_p_sortColIdx,
						   Oid **fc_p_sortOperators,
						   Oid **fc_p_collations,
						   bool **fc_p_nullsFirst)
{
	List	   *fc_tlist = fc_lefttree->targetlist;
	ListCell   *fc_i;
	int			fc_numsortkeys;
	AttrNumber *fc_sortColIdx;
	Oid		   *fc_sortOperators;
	Oid		   *fc_collations;
	bool	   *fc_nullsFirst;

	/*
	 * 我们最多需要 list_length(pathkeys) 个排序列；可能更少
	 */
	fc_numsortkeys = list_length(fc_pathkeys);
	fc_sortColIdx = (AttrNumber *) palloc(fc_numsortkeys * sizeof(AttrNumber));
	fc_sortOperators = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_collations = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_nullsFirst = (bool *) palloc(fc_numsortkeys * sizeof(bool));

	fc_numsortkeys = 0;

	foreach(fc_i, fc_pathkeys)
	{
		PathKey    *fc_pathkey = (PathKey *) lfirst(fc_i);
		EquivalenceClass *fc_ec = fc_pathkey->pk_eclass;
		EquivalenceMember *fc_em;
		TargetEntry *fc_tle = NULL;
		Oid			fc_pk_datatype = InvalidOid;
		Oid			fc_sortop;
		ListCell   *fc_j;

		if (fc_ec->ec_has_volatile)
		{
			/*
			 * 如果 pathkey 的 EquivalenceClass 是动态的，那么它必须
			 * 来自 ORDER BY 子句，我们必须将其匹配到
			 * 那个相同的目标列表条目。
			 */
			if (fc_ec->ec_sortref == 0)	/* 不能发生 */
				elog(ERROR, "volatile EquivalenceClass has no sortref");
			fc_tle = get_sortgroupref_tle(fc_ec->ec_sortref, fc_tlist);
			Assert(fc_tle);
			Assert(list_length(fc_ec->ec_members) == 1);
			fc_pk_datatype = ((EquivalenceMember *) linitial(fc_ec->ec_members))->em_datatype;
		}
		else if (fc_reqColIdx != NULL)
		{
			/*
			 * 如果我们被给定了要匹配的排序列号，仅考虑
			 * 该位置的单个 TLE。可能没有这样的 TLE，
			 * 此时将继续并生成一个 resjunk 目标条目（我们假设这在父
			 * 计划中也必然发生）。如果有 TLE 但它不匹配
			 * pathkey 的 EC，我们也这样做，这可能是错误的，
			 * 但我们将留给调用者来对不匹配进行投诉。
			 */
			fc_tle = get_tle_by_resno(fc_tlist, fc_reqColIdx[fc_numsortkeys]);
			if (fc_tle)
			{
				fc_em = find_ec_member_matching_expr(fc_ec, fc_tle->expr, fc_relids);
				if (fc_em)
				{
					/* 在目标列表的正确位置找到表达式 */
					fc_pk_datatype = fc_em->em_datatype;
				}
				else
					fc_tle = NULL;
			}
		}
		else
		{
			/*
			 * 否则，我们可以按照 pathkey 的 EquivalenceClass 中列出的
			 * 任何非恒定表达式进行排序。现在，我们取在 EC 中找到的第一个
			 * 目标列表项。如果没有匹配，我们将使用输入变量中的第一个
			 * 表达式成员生成一个 resjunk 条目。（非恒定限制仅在 EC 处于 below_outer_join 时重要；
			 * 但是如果不是，它不会包含常量，否则我们会将 pathkey 作为冗余丢弃。）
			 *
			 * XXX 如果我们有选择，有没有办法找出哪个
			 * 可能执行成本最低？（例如，int4lt 可能比
			 * numericlt 执行成本低得多，但两者可能出现在同一个等价类中...）
			 * 在实践中不清楚我们是否会有有趣的选择，所以这可能无关紧要。
			 */
			foreach(fc_j, fc_tlist)
			{
				fc_tle = (TargetEntry *) lfirst(fc_j);
				fc_em = find_ec_member_matching_expr(fc_ec, fc_tle->expr, fc_relids);
				if (fc_em)
				{
					/* 表达式已经在目标列表中找到 */
					fc_pk_datatype = fc_em->em_datatype;
					break;
				}
				fc_tle = NULL;
			}
		}

		if (!fc_tle)
		{
			/*
			 * 没有匹配的目标列表项；查找一个可计算的表达式。
			 */
			fc_em = find_computable_ec_member(NULL, fc_ec, fc_tlist, fc_relids, false);
			if (!fc_em)
				elog(ERROR, "could not find pathkey item to sort");
			fc_pk_datatype = fc_em->em_datatype;

			/*
			 * 我们需要插入一个结果节点吗？
			 */
			if (!fc_adjust_tlist_in_place &&
				!is_projection_capable_plan(fc_lefttree))
			{
				/* 需要复制，以便我们不修改输入的目标列表 */
				fc_tlist = copyObject(fc_tlist);
				fc_lefttree = fc_inject_projection_plan(fc_lefttree, fc_tlist,
												  fc_lefttree->parallel_safe);
			}

			/* 不再测试 is_projection_capable_plan */
			fc_adjust_tlist_in_place = true;

			/*
			 * 向输入目标列表添加 resjunk 条目
			 */
			fc_tle = makeTargetEntry(copyObject(fc_em->em_expr),
								  list_length(fc_tlist) + 1,
								  NULL,
								  true);
			fc_tlist = lappend(fc_tlist, fc_tle);
			fc_lefttree->targetlist = fc_tlist;	/* 以防 NIL 在前 */
		}

		/*
		 * 根据 PathKey 的稍微抽象的表示查找正确的排序操作符。
		 */
		fc_sortop = get_opfamily_member(fc_pathkey->pk_opfamily,
									 fc_pk_datatype,
									 fc_pk_datatype,
									 fc_pathkey->pk_strategy);
		if (!OidIsValid(fc_sortop))	/* 不应该发生 */
			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
				 fc_pathkey->pk_strategy, fc_pk_datatype, fc_pk_datatype,
				 fc_pathkey->pk_opfamily);

		/* 将列添加到排序数组中 */
		fc_sortColIdx[fc_numsortkeys] = fc_tle->resno;
		fc_sortOperators[fc_numsortkeys] = fc_sortop;
		fc_collations[fc_numsortkeys] = fc_ec->ec_collation;
		fc_nullsFirst[fc_numsortkeys] = fc_pathkey->pk_nulls_first;
		fc_numsortkeys++;
	}

	/* 返回结果 */
	*fc_p_numsortkeys = fc_numsortkeys;
	*fc_p_sortColIdx = fc_sortColIdx;
	*fc_p_sortOperators = fc_sortOperators;
	*fc_p_collations = fc_collations;
	*fc_p_nullsFirst = fc_nullsFirst;

	return fc_lefttree;
}


/*
 * make_sort_from_pathkeys
 *	  创建排序计划以根据给定的路径键进行排序
 *
 *	  'lefttree' 是生成输入元组的节点
 *	  'pathkeys' 是结果将按其排序的路径键列表
 *	  'relids' 是 prepare_sort_from_pathkeys() 所需的关系集
 */
static Sort * fc_make_sort_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys, Relids fc_relids)
{
	int			fc_numsortkeys;
	AttrNumber *fc_sortColIdx;
	Oid		   *fc_sortOperators;
	Oid		   *fc_collations;
	bool	   *fc_nullsFirst;

	/* 计算排序列信息，并根据需要调整 lefttree */
	fc_lefttree = fc_prepare_sort_from_pathkeys(fc_lefttree, fc_pathkeys,
										  fc_relids,
										  NULL,
										  false,
										  &fc_numsortkeys,
										  &fc_sortColIdx,
										  &fc_sortOperators,
										  &fc_collations,
										  &fc_nullsFirst);

	/* 现在构建排序节点 */
	return fc_make_sort(fc_lefttree, fc_numsortkeys,
					 fc_sortColIdx, fc_sortOperators,
					 fc_collations, fc_nullsFirst);
}

/*
 * make_incrementalsort_from_pathkeys
 *	  创建排序计划以根据给定的路径键进行排序
 *
 *	  'lefttree' 是生成输入元组的节点
 *	  'pathkeys' 是结果将按其排序的路径键列表
 *	  'relids' 是 prepare_sort_from_pathkeys() 所需的关系集
 *	  'nPresortedCols' 是输入元组中预排序列的数量
 */
static IncrementalSort * fc_make_incrementalsort_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys,
								   Relids fc_relids, int fc_nPresortedCols)
{
	int			fc_numsortkeys;
	AttrNumber *fc_sortColIdx;
	Oid		   *fc_sortOperators;
	Oid		   *fc_collations;
	bool	   *fc_nullsFirst;

	/* 计算排序列信息，并根据需要调整 lefttree */
	fc_lefttree = fc_prepare_sort_from_pathkeys(fc_lefttree, fc_pathkeys,
										  fc_relids,
										  NULL,
										  false,
										  &fc_numsortkeys,
										  &fc_sortColIdx,
										  &fc_sortOperators,
										  &fc_collations,
										  &fc_nullsFirst);

	/* 现在构建排序节点 */
	return fc_make_incrementalsort(fc_lefttree, fc_numsortkeys, fc_nPresortedCols,
								fc_sortColIdx, fc_sortOperators,
								fc_collations, fc_nullsFirst);
}

/*
 * make_sort_from_sortclauses
 *	  创建排序计划以根据给定的排序子句进行排序
 *
 *	  'sortcls' 是一组 SortGroupClauses 的列表
 *	  'lefttree' 是生成输入元组的节点
 */
Sort *
make_sort_from_sortclauses(List *fc_sortcls, Plan *fc_lefttree)
{
	List	   *fc_sub_tlist = fc_lefttree->targetlist;
	ListCell   *fc_l;
	int			fc_numsortkeys;
	AttrNumber *fc_sortColIdx;
	Oid		   *fc_sortOperators;
	Oid		   *fc_collations;
	bool	   *fc_nullsFirst;

	/* 将类列表表示转换为执行者所需的数组 */
	fc_numsortkeys = list_length(fc_sortcls);
	fc_sortColIdx = (AttrNumber *) palloc(fc_numsortkeys * sizeof(AttrNumber));
	fc_sortOperators = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_collations = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_nullsFirst = (bool *) palloc(fc_numsortkeys * sizeof(bool));

	fc_numsortkeys = 0;
	foreach(fc_l, fc_sortcls)
	{
		SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_l);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl, fc_sub_tlist);

		fc_sortColIdx[fc_numsortkeys] = fc_tle->resno;
		fc_sortOperators[fc_numsortkeys] = fc_sortcl->sortop;
		fc_collations[fc_numsortkeys] = exprCollation((Node *) fc_tle->expr);
		fc_nullsFirst[fc_numsortkeys] = fc_sortcl->nulls_first;
		fc_numsortkeys++;
	}

	return fc_make_sort(fc_lefttree, fc_numsortkeys,
					 fc_sortColIdx, fc_sortOperators,
					 fc_collations, fc_nullsFirst);
}

/*
 * make_sort_from_groupcols
 *	  创建排序计划以基于分组列进行排序
 *
 * 'groupcls' 是 SortGroupClauses 的列表
 * 'grpColIdx' 给出要使用的列号
 *
 * 这看起来像可以与 make_sort_from_sortclauses 合并，
 * 但目前我们 *必须* 使用 grpColIdx[] 数组来定位排序列，
 * 因为子计划的 tlist 没有标记与分组节点相应的 ressortgroupref 信息。
 * 因此，仅使用 SortGroupClause 条目的排序顺序信息。
 */
static Sort * fc_make_sort_from_groupcols(List *fc_groupcls,
						 AttrNumber *fc_grpColIdx,
						 Plan *fc_lefttree)
{
	List	   *fc_sub_tlist = fc_lefttree->targetlist;
	ListCell   *fc_l;
	int			fc_numsortkeys;
	AttrNumber *fc_sortColIdx;
	Oid		   *fc_sortOperators;
	Oid		   *fc_collations;
	bool	   *fc_nullsFirst;

	/* 将类列表表示转换为执行者所需的数组 */
	fc_numsortkeys = list_length(fc_groupcls);
	fc_sortColIdx = (AttrNumber *) palloc(fc_numsortkeys * sizeof(AttrNumber));
	fc_sortOperators = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_collations = (Oid *) palloc(fc_numsortkeys * sizeof(Oid));
	fc_nullsFirst = (bool *) palloc(fc_numsortkeys * sizeof(bool));

	fc_numsortkeys = 0;
	foreach(fc_l, fc_groupcls)
	{
		SortGroupClause *fc_grpcl = (SortGroupClause *) lfirst(fc_l);
		TargetEntry *fc_tle = get_tle_by_resno(fc_sub_tlist, fc_grpColIdx[fc_numsortkeys]);

		if (!fc_tle)
			elog(ERROR, "could not retrieve tle for sort-from-groupcols");

		fc_sortColIdx[fc_numsortkeys] = fc_tle->resno;
		fc_sortOperators[fc_numsortkeys] = fc_grpcl->sortop;
		fc_collations[fc_numsortkeys] = exprCollation((Node *) fc_tle->expr);
		fc_nullsFirst[fc_numsortkeys] = fc_grpcl->nulls_first;
		fc_numsortkeys++;
	}

	return fc_make_sort(fc_lefttree, fc_numsortkeys,
					 fc_sortColIdx, fc_sortOperators,
					 fc_collations, fc_nullsFirst);
}

static Material * fc_make_material(Plan *fc_lefttree)
{
	Material   *fc_node = makeNode(Material);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	return fc_node;
}

/*
 * materialize_finished_plan: 在完成的计划上方添加物化节点
 *
 * 有几个地方我们希望在 create_plan() 完成后附加物化节点，
 * 而无需任何 MaterialPath 路径。
 * 这些地方可能在将来重构，以在路径表示上执行此操作，
 * 但现在还不值得麻烦。
 */
Plan * materialize_finished_plan(Plan *fc_subplan)
{
	Plan	   *fc_matplan;
	Path		fc_matpath;		/* 成本物料成绩的占位符 */

	fc_matplan = (Plan *) fc_make_material(fc_subplan);

	/*
	 * XXX 糟糕的临时解决方案：如果有任何 initPlans 附加到子计划，
	 * 将它们移动到物化节点上，该节点现在有效地是其查询级别中的顶层
	 * 计划节点。这防止在 SS_finalize_plan() 中失败，详见评论。我们不调整
	 * 子计划的成本估计。
	 */
	fc_matplan->initPlan = fc_subplan->initPlan;
	fc_subplan->initPlan = NIL;

	/* 设置成本数据 */
	cost_material(&fc_matpath,
				  fc_subplan->startup_cost,
				  fc_subplan->total_cost,
				  fc_subplan->plan_rows,
				  fc_subplan->plan_width);
	fc_matplan->startup_cost = fc_matpath.startup_cost;
	fc_matplan->total_cost = fc_matpath.total_cost;
	fc_matplan->plan_rows = fc_subplan->plan_rows;
	fc_matplan->plan_width = fc_subplan->plan_width;
	fc_matplan->parallel_aware = false;
	fc_matplan->parallel_safe = fc_subplan->parallel_safe;

	return fc_matplan;
}

static Memoize * fc_make_memoize(Plan *fc_lefttree, Oid *fc_hashoperators, Oid *fc_collations,
			 List *fc_param_exprs, bool fc_singlerow, bool fc_binary_mode,
			 uint32 fc_est_entries, Bitmapset *fc_keyparamids)
{
	Memoize    *fc_node = makeNode(Memoize);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	fc_node->numKeys = list_length(fc_param_exprs);
	fc_node->hashOperators = fc_hashoperators;
	fc_node->collations = fc_collations;
	fc_node->param_exprs = fc_param_exprs;
	fc_node->singlerow = fc_singlerow;
	fc_node->binary_mode = fc_binary_mode;
	fc_node->est_entries = fc_est_entries;
	fc_node->keyparamids = fc_keyparamids;

	return fc_node;
}

Agg *
make_agg(List *fc_tlist, List *fc_qual,
		 AggStrategy fc_aggstrategy, AggSplit fc_aggsplit,
		 int fc_numGroupCols, AttrNumber *fc_grpColIdx, Oid *fc_grpOperators, Oid *fc_grpCollations,
		 List *fc_groupingSets, List *fc_chain, double fc_dNumGroups,
		 Size fc_transitionSpace, Plan *fc_lefttree)
{
	Agg		   *fc_node = makeNode(Agg);
	Plan	   *fc_plan = &fc_node->plan;
	long		fc_numGroups;

	/* 简化为长整型，但当心溢出！ */
	fc_numGroups = clamp_cardinality_to_long(fc_dNumGroups);

	fc_node->aggstrategy = fc_aggstrategy;
	fc_node->aggsplit = fc_aggsplit;
	fc_node->numCols = fc_numGroupCols;
	fc_node->grpColIdx = fc_grpColIdx;
	fc_node->grpOperators = fc_grpOperators;
	fc_node->grpCollations = fc_grpCollations;
	fc_node->numGroups = fc_numGroups;
	fc_node->transitionSpace = fc_transitionSpace;
	fc_node->aggParams = NULL;		
/* SS_finalize_plan() 将填充这个 */
	fc_node->groupingSets = fc_groupingSets;
	fc_node->chain = fc_chain;

	fc_plan->qual = fc_qual;
	fc_plan->targetlist = fc_tlist;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	return fc_node;
}

static WindowAgg * fc_make_windowagg(List *fc_tlist, Index fc_winref,
			   int fc_partNumCols, AttrNumber *fc_partColIdx, Oid *fc_partOperators, Oid *fc_partCollations,
			   int fc_ordNumCols, AttrNumber *fc_ordColIdx, Oid *fc_ordOperators, Oid *fc_ordCollations,
			   int fc_frameOptions, Node *fc_startOffset, Node *fc_endOffset,
			   Oid fc_startInRangeFunc, Oid fc_endInRangeFunc,
			   Oid fc_inRangeColl, bool fc_inRangeAsc, bool fc_inRangeNullsFirst,
			   List *fc_runCondition, List *fc_qual, bool fc_topWindow, Plan *fc_lefttree)
{
	WindowAgg  *fc_node = makeNode(WindowAgg);
	Plan	   *fc_plan = &fc_node->plan;

	fc_node->winref = fc_winref;
	fc_node->partNumCols = fc_partNumCols;
	fc_node->partColIdx = fc_partColIdx;
	fc_node->partOperators = fc_partOperators;
	fc_node->partCollations = fc_partCollations;
	fc_node->ordNumCols = fc_ordNumCols;
	fc_node->ordColIdx = fc_ordColIdx;
	fc_node->ordOperators = fc_ordOperators;
	fc_node->ordCollations = fc_ordCollations;
	fc_node->frameOptions = fc_frameOptions;
	fc_node->startOffset = fc_startOffset;
	fc_node->endOffset = fc_endOffset;
	fc_node->runCondition = fc_runCondition;
	/* 上面的一个重复，用于 EXPLAIN */
	fc_node->runConditionOrig = fc_runCondition;
	fc_node->startInRangeFunc = fc_startInRangeFunc;
	fc_node->endInRangeFunc = fc_endInRangeFunc;
	fc_node->inRangeColl = fc_inRangeColl;
	fc_node->inRangeAsc = fc_inRangeAsc;
	fc_node->inRangeNullsFirst = fc_inRangeNullsFirst;
	fc_node->topWindow = fc_topWindow;

	fc_plan->targetlist = fc_tlist;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;
	fc_plan->qual = fc_qual;

	return fc_node;
}

static Group * fc_make_group(List *fc_tlist,
		   List *fc_qual,
		   int fc_numGroupCols,
		   AttrNumber *fc_grpColIdx,
		   Oid *fc_grpOperators,
		   Oid *fc_grpCollations,
		   Plan *fc_lefttree)
{
	Group	   *fc_node = makeNode(Group);
	Plan	   *fc_plan = &fc_node->plan;

	fc_node->numCols = fc_numGroupCols;
	fc_node->grpColIdx = fc_grpColIdx;
	fc_node->grpOperators = fc_grpOperators;
	fc_node->grpCollations = fc_grpCollations;

	fc_plan->qual = fc_qual;
	fc_plan->targetlist = fc_tlist;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	return fc_node;
}

/*
 * distinctList 是一个 SortGroupClauses 的列表，标识应该被 Unique 过滤器考虑的目标列表项。输入路径必须已经相应地排序。
 */
static Unique * fc_make_unique_from_sortclauses(Plan *fc_lefttree, List *fc_distinctList)
{
	Unique	   *fc_node = makeNode(Unique);
	Plan	   *fc_plan = &fc_node->plan;
	int			fc_numCols = list_length(fc_distinctList);
	int			fc_keyno = 0;
	AttrNumber *fc_uniqColIdx;
	Oid		   *fc_uniqOperators;
	Oid		   *fc_uniqCollations;
	ListCell   *fc_slitem;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	/*
	 * 将 SortGroupClause 列表转换为属性索引和相等操作符的数组，如执行器所需
	 */
	Assert(fc_numCols > 0);
	fc_uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numCols);
	fc_uniqOperators = (Oid *) palloc(sizeof(Oid) * fc_numCols);
	fc_uniqCollations = (Oid *) palloc(sizeof(Oid) * fc_numCols);

	foreach(fc_slitem, fc_distinctList)
	{
		SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_slitem);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl, fc_plan->targetlist);

		fc_uniqColIdx[fc_keyno] = fc_tle->resno;
		fc_uniqOperators[fc_keyno] = fc_sortcl->eqop;
		fc_uniqCollations[fc_keyno] = exprCollation((Node *) fc_tle->expr);
		Assert(OidIsValid(fc_uniqOperators[fc_keyno]));
		fc_keyno++;
	}

	fc_node->numCols = fc_numCols;
	fc_node->uniqColIdx = fc_uniqColIdx;
	fc_node->uniqOperators = fc_uniqOperators;
	fc_node->uniqCollations = fc_uniqCollations;

	return fc_node;
}

/*
 * 与上面相同，但使用 pathkeys 来识别排序列和语义
 */
static Unique * fc_make_unique_from_pathkeys(Plan *fc_lefttree, List *fc_pathkeys, int fc_numCols)
{
	Unique	   *fc_node = makeNode(Unique);
	Plan	   *fc_plan = &fc_node->plan;
	int			fc_keyno = 0;
	AttrNumber *fc_uniqColIdx;
	Oid		   *fc_uniqOperators;
	Oid		   *fc_uniqCollations;
	ListCell   *fc_lc;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	/*
	 * 将 pathkeys 列表转换为属性索引和相等运算符的数组，正如执行器所需要的。这与 prepare_sort_from_pathkeys 有很多共同点……也许有时可以统一？
	 */
	Assert(fc_numCols >= 0 && fc_numCols <= list_length(fc_pathkeys));
	fc_uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numCols);
	fc_uniqOperators = (Oid *) palloc(sizeof(Oid) * fc_numCols);
	fc_uniqCollations = (Oid *) palloc(sizeof(Oid) * fc_numCols);

	foreach(fc_lc, fc_pathkeys)
	{
		PathKey    *fc_pathkey = (PathKey *) lfirst(fc_lc);
		EquivalenceClass *fc_ec = fc_pathkey->pk_eclass;
		EquivalenceMember *fc_em;
		TargetEntry *fc_tle = NULL;
		Oid			fc_pk_datatype = InvalidOid;
		Oid			fc_eqop;
		ListCell   *fc_j;

		/* 忽略超过指定列数的 pathkeys */
		if (fc_keyno >= fc_numCols)
			break;

		if (fc_ec->ec_has_volatile)
		{
			/*
			 * 如果 pathkey 的 EquivalenceClass 是动态的，那么它必须
			 * 来自 ORDER BY 子句，我们必须将其匹配到
			 * 那个相同的目标列表条目。
			 */
			if (fc_ec->ec_sortref == 0)	/* 不能发生 */
				elog(ERROR, "volatile EquivalenceClass has no sortref");
			fc_tle = get_sortgroupref_tle(fc_ec->ec_sortref, fc_plan->targetlist);
			Assert(fc_tle);
			Assert(list_length(fc_ec->ec_members) == 1);
			fc_pk_datatype = ((EquivalenceMember *) linitial(fc_ec->ec_members))->em_datatype;
		}
		else
		{
			/*
			 * 否则，我们可以使用 pathkey 的 EquivalenceClass 中列出的任何非恒定表达式。现在，我们采用在 EC 中找到的第一个目标列表项。
			 */
			foreach(fc_j, fc_plan->targetlist)
			{
				fc_tle = (TargetEntry *) lfirst(fc_j);
				fc_em = find_ec_member_matching_expr(fc_ec, fc_tle->expr, NULL);
				if (fc_em)
				{
					/* 表达式已经在目标列表中找到 */
					fc_pk_datatype = fc_em->em_datatype;
					break;
				}
				fc_tle = NULL;
			}
		}

		if (!fc_tle)
			elog(ERROR, "could not find pathkey item to sort");

		/*
		 * 从 PathKey 的稍微抽象的表示中查找正确的相等运算符。
		 */
		fc_eqop = get_opfamily_member(fc_pathkey->pk_opfamily,
								   fc_pk_datatype,
								   fc_pk_datatype,
								   BTEqualStrategyNumber);
		if (!OidIsValid(fc_eqop))	/* 不应该发生 */
			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
				 BTEqualStrategyNumber, fc_pk_datatype, fc_pk_datatype,
				 fc_pathkey->pk_opfamily);

		fc_uniqColIdx[fc_keyno] = fc_tle->resno;
		fc_uniqOperators[fc_keyno] = fc_eqop;
		fc_uniqCollations[fc_keyno] = fc_ec->ec_collation;

		fc_keyno++;
	}

	fc_node->numCols = fc_numCols;
	fc_node->uniqColIdx = fc_uniqColIdx;
	fc_node->uniqOperators = fc_uniqOperators;
	fc_node->uniqCollations = fc_uniqCollations;

	return fc_node;
}

static Gather * fc_make_gather(List *fc_qptlist,
			List *fc_qpqual,
			int fc_nworkers,
			int fc_rescan_param,
			bool fc_single_copy,
			Plan *fc_subplan)
{
	Gather	   *fc_node = makeNode(Gather);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_qptlist;
	fc_plan->qual = fc_qpqual;
	fc_plan->lefttree = fc_subplan;
	fc_plan->righttree = NULL;
	fc_node->num_workers = fc_nworkers;
	fc_node->rescan_param = fc_rescan_param;
	fc_node->single_copy = fc_single_copy;
	fc_node->invisible = false;
	fc_node->initParam = NULL;

	return fc_node;
}

/*
 * distinctList 是一个 SortGroupClauses 的列表，标识应该被 SetOp 过滤器考虑的目标列表项。输入路径必须已经相应地排序。
 */
static SetOp * fc_make_setop(SetOpCmd fc_cmd, SetOpStrategy fc_strategy, Plan *fc_lefttree,
		   List *fc_distinctList, AttrNumber fc_flagColIdx, int fc_firstFlag,
		   long fc_numGroups)
{
	SetOp	   *fc_node = makeNode(SetOp);
	Plan	   *fc_plan = &fc_node->plan;
	int			fc_numCols = list_length(fc_distinctList);
	int			fc_keyno = 0;
	AttrNumber *fc_dupColIdx;
	Oid		   *fc_dupOperators;
	Oid		   *fc_dupCollations;
	ListCell   *fc_slitem;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	/*
	 * 将 SortGroupClause 列表转换为属性索引和相等操作符的数组，如执行器所需
	 */
	fc_dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * fc_numCols);
	fc_dupOperators = (Oid *) palloc(sizeof(Oid) * fc_numCols);
	fc_dupCollations = (Oid *) palloc(sizeof(Oid) * fc_numCols);

	foreach(fc_slitem, fc_distinctList)
	{
		SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_slitem);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl, fc_plan->targetlist);

		fc_dupColIdx[fc_keyno] = fc_tle->resno;
		fc_dupOperators[fc_keyno] = fc_sortcl->eqop;
		fc_dupCollations[fc_keyno] = exprCollation((Node *) fc_tle->expr);
		Assert(OidIsValid(fc_dupOperators[fc_keyno]));
		fc_keyno++;
	}

	fc_node->cmd = fc_cmd;
	fc_node->strategy = fc_strategy;
	fc_node->numCols = fc_numCols;
	fc_node->dupColIdx = fc_dupColIdx;
	fc_node->dupOperators = fc_dupOperators;
	fc_node->dupCollations = fc_dupCollations;
	fc_node->flagColIdx = fc_flagColIdx;
	fc_node->firstFlag = fc_firstFlag;
	fc_node->numGroups = fc_numGroups;

	return fc_node;
}

/*
 * make_lockrows
 *	  构建一个 LockRows 计划节点
 */
static LockRows * fc_make_lockrows(Plan *fc_lefttree, List *fc_rowMarks, int fc_epqParam)
{
	LockRows   *fc_node = makeNode(LockRows);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	fc_node->rowMarks = fc_rowMarks;
	fc_node->epqParam = fc_epqParam;

	return fc_node;
}

/*
 * make_limit
 *	  构建一个 Limit 计划节点
 */
Limit *
make_limit(Plan *fc_lefttree, Node *fc_limitOffset, Node *fc_limitCount,
		   LimitOption fc_limitOption, int fc_uniqNumCols, AttrNumber *fc_uniqColIdx,
		   Oid *fc_uniqOperators, Oid *fc_uniqCollations)
{
	Limit	   *fc_node = makeNode(Limit);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_lefttree->targetlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_lefttree;
	fc_plan->righttree = NULL;

	fc_node->limitOffset = fc_limitOffset;
	fc_node->limitCount = fc_limitCount;
	fc_node->limitOption = fc_limitOption;
	fc_node->uniqNumCols = fc_uniqNumCols;
	fc_node->uniqColIdx = fc_uniqColIdx;
	fc_node->uniqOperators = fc_uniqOperators;
	fc_node->uniqCollations = fc_uniqCollations;

	return fc_node;
}

/*
 * make_result
 *	  构建一个 Result 计划节点
 */
static Result * fc_make_result(List *fc_tlist,
			Node *fc_resconstantqual,
			Plan *fc_subplan)
{
	Result	   *fc_node = makeNode(Result);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_subplan;
	fc_plan->righttree = NULL;
	fc_node->resconstantqual = fc_resconstantqual;

	return fc_node;
}

/*
 * make_project_set
 *	  构建一个 ProjectSet 计划节点
 */
static ProjectSet * fc_make_project_set(List *fc_tlist,
				 Plan *fc_subplan)
{
	ProjectSet *fc_node = makeNode(ProjectSet);
	Plan	   *fc_plan = &fc_node->plan;

	fc_plan->targetlist = fc_tlist;
	fc_plan->qual = NIL;
	fc_plan->lefttree = fc_subplan;
	fc_plan->righttree = NULL;

	return fc_node;
}

/*
 * make_modifytable
 *	  构建一个 ModifyTable 计划节点
 */
static ModifyTable * fc_make_modifytable(PlannerInfo *fc_root, Plan *fc_subplan,
				 CmdType fc_operation, bool fc_canSetTag,
				 Index fc_nominalRelation, Index fc_rootRelation,
				 bool fc_partColsUpdated,
				 List *fc_resultRelations,
				 List *fc_updateColnosLists,
				 List *fc_withCheckOptionLists, List *fc_returningLists,
				 List *fc_rowMarks, OnConflictExpr *fc_onconflict,
				 List *fc_mergeActionLists, int fc_epqParam)
{
	ModifyTable *fc_node = makeNode(ModifyTable);
	List	   *fc_fdw_private_list;
	Bitmapset  *fc_direct_modify_plans;
	ListCell   *fc_lc;
	int			fc_i;

	Assert(fc_operation == CMD_MERGE ||
		   (fc_operation == CMD_UPDATE ?
			list_length(fc_resultRelations) == list_length(fc_updateColnosLists) :
			fc_updateColnosLists == NIL));
	Assert(fc_withCheckOptionLists == NIL ||
		   list_length(fc_resultRelations) == list_length(fc_withCheckOptionLists));
	Assert(fc_returningLists == NIL ||
		   list_length(fc_resultRelations) == list_length(fc_returningLists));

	fc_node->plan.lefttree = fc_subplan;
	fc_node->plan.righttree = NULL;
	fc_node->plan.qual = NIL;
	/* setrefs.c 会在必要时填充目标列表 */
	fc_node->plan.targetlist = NIL;

	fc_node->operation = fc_operation;
	fc_node->canSetTag = fc_canSetTag;
	fc_node->nominalRelation = fc_nominalRelation;
	fc_node->rootRelation = fc_rootRelation;
	fc_node->partColsUpdated = fc_partColsUpdated;
	fc_node->resultRelations = fc_resultRelations;
	if (!fc_onconflict)
	{
		fc_node->onConflictAction = ONCONFLICT_NONE;
		fc_node->onConflictSet = NIL;
		fc_node->onConflictCols = NIL;
		fc_node->onConflictWhere = NULL;
		fc_node->arbiterIndexes = NIL;
		fc_node->exclRelRTI = 0;
		fc_node->exclRelTlist = NIL;
	}
	else
	{
		fc_node->onConflictAction = fc_onconflict->action;

		/*
		 * 在这里，我们将 ON CONFLICT UPDATE 目标列表（如果有）转换为执行器的惯例，即具有连续的 resno。实际的目标列号保存在 node->onConflictCols 中。（这可以在前面完成，但似乎没有必要。）
		 */
		fc_node->onConflictSet = fc_onconflict->onConflictSet;
		fc_node->onConflictCols =
			extract_update_targetlist_colnos(fc_node->onConflictSet);
		fc_node->onConflictWhere = fc_onconflict->onConflictWhere;

		/*
		 * 如果提供了一组唯一索引推断元素（一个 INSERT...ON CONFLICT "推断规范"），则推断适当的唯一索引（如果没有可用的，则抛出错误）。
		 */
		fc_node->arbiterIndexes = infer_arbiter_indexes(fc_root);

		fc_node->exclRelRTI = fc_onconflict->exclRelIndex;
		fc_node->exclRelTlist = fc_onconflict->exclRelTlist;
	}
	fc_node->updateColnosLists = fc_updateColnosLists;
	fc_node->withCheckOptionLists = fc_withCheckOptionLists;
	fc_node->returningLists = fc_returningLists;
	fc_node->rowMarks = fc_rowMarks;
	fc_node->mergeActionLists = fc_mergeActionLists;
	fc_node->epqParam = fc_epqParam;

	/*
	 * 对于每个作为外部表的结果关系，允许 FDW 构建私有计划数据，并将其累积到一个列表中。
	 */
	fc_fdw_private_list = NIL;
	fc_direct_modify_plans = NULL;
	fc_i = 0;
	foreach(fc_lc, fc_resultRelations)
	{
		Index		fc_rti = lfirst_int(fc_lc);
		FdwRoutine *fc_fdwroutine;
		List	   *fc_fdw_private;
		bool		fc_direct_modify;

		/*
		 * 如果可能，我们希望从我们 RelOptInfo 获取表的 FdwRoutine。但是有时我们没有 RelOptInfo，必须通过困难的方式获取它。（在 INSERT中，目标关系未扫描，因此它不是 baserel；并且对于可更新视图，目标关系也不是 baserel 也存在边缘情况。）
		 */
		if (fc_rti < fc_root->simple_rel_array_size &&
			fc_root->simple_rel_array[fc_rti] != NULL)
		{
			RelOptInfo *fc_resultRel = fc_root->simple_rel_array[fc_rti];

			fc_fdwroutine = fc_resultRel->fdwroutine;

			/*
			 * 当前不支持 MERGE 外部表，并且我们已经在查询中检查了提到的表是否是外部的；但是如果一个分区表的分区是外部表，我们仍然可能到达这里。现在禁止这样做，以避免以后出现更丑陋的错误消息。
			 */
			if (fc_operation == CMD_MERGE && fc_fdwroutine != NULL)
			{
				RangeTblEntry *fc_rte = fc_root->simple_rte_array[fc_rti];

				ereport(ERROR,
						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						errmsg("cannot execute MERGE on relation \"%s\"",
							   get_rel_name(fc_rte->relid)),
						errdetail_relkind_not_supported(fc_rte->relkind));
			}

		}
		else
		{
			RangeTblEntry *fc_rte = planner_rt_fetch(fc_rti, fc_root);

			Assert(fc_rte->rtekind == RTE_RELATION);
			Assert(fc_operation != CMD_MERGE);
			if (fc_rte->relkind == RELKIND_FOREIGN_TABLE)
			{
				/* 检查对外部表的访问是否受到限制 */
				if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
				{
					/* 不得有内置的外部表 */
					Assert(fc_rte->relid >= FirstNormalObjectId);
					ereport(ERROR,
							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
							 errmsg("access to non-system foreign table is restricted")));
				}

				fc_fdwroutine = GetFdwRoutineByRelId(fc_rte->relid);
			}
			else
				fc_fdwroutine = NULL;
		}

		/*
		 * 如果 (1) FDW 提供所需的回调函数并且 (2) 没有需要对每个修改的行运行的本地结构：外部表上的行级触发器、存储生成的列、父视图中的 WITH CHECK OPTION。
		 */
		fc_direct_modify = false;
		if (fc_fdwroutine != NULL &&
			fc_fdwroutine->PlanDirectModify != NULL &&
			fc_fdwroutine->BeginDirectModify != NULL &&
			fc_fdwroutine->IterateDirectModify != NULL &&
			fc_fdwroutine->EndDirectModify != NULL &&
			fc_withCheckOptionLists == NIL &&
			!has_row_triggers(fc_root, fc_rti, fc_operation) &&
			!has_stored_generated_columns(fc_root, fc_rti))
			fc_direct_modify = fc_fdwroutine->PlanDirectModify(fc_root, fc_node, fc_rti, fc_i);
		if (fc_direct_modify)
			fc_direct_modify_plans = bms_add_member(fc_direct_modify_plans, fc_i);

		if (!fc_direct_modify &&
			fc_fdwroutine != NULL &&
			fc_fdwroutine->PlanForeignModify != NULL)
			fc_fdw_private = fc_fdwroutine->PlanForeignModify(fc_root, fc_node, fc_rti, fc_i);
		else
			fc_fdw_private = NIL;
		fc_fdw_private_list = lappend(fc_fdw_private_list, fc_fdw_private);
		fc_i++;
	}
	fc_node->fdwPrivLists = fc_fdw_private_list;
	fc_node->fdwDirectModifyPlans = fc_direct_modify_plans;

	return fc_node;
}

/*
 * is_projection_capable_path
 *		检查给定的 Path 节点是否能够进行投影。
 */
bool is_projection_capable_path(Path *fc_path)
{
	/* 大多数计划类型都可以投影，因此只列出不能的那些 */
	switch (fc_path->pathtype)
	{
		case T_Hash:
		case T_Material:
		case T_Memoize:
		case T_Sort:
		case T_IncrementalSort:
		case T_Unique:
		case T_SetOp:
		case T_LockRows:
		case T_Limit:
		case T_ModifyTable:
		case T_MergeAppend:
		case T_RecursiveUnion:
			return false;
		case T_CustomScan:
			if (castNode(CustomPath, fc_path)->flags & CUSTOMPATH_SUPPORT_PROJECTION)
				return true;
			return false;
		case T_Append:

			/*
			 * Append 不能投影，但如果使用 AppendPath 来表示虚拟路径，实际上生成的将是一个可以投影的 Result。
			 */
			return IS_DUMMY_APPEND(fc_path);
		case T_ProjectSet:

			/*
			 * 尽管 ProjectSet 当然可以投影，但我们还是说“不”，因为我们不希望规划器随机替换其目标列表为其他内容；SRF 必须保留在顶部。这可能在稍后放宽。
			 */
			return false;
		default:
			break;
	}
	return true;
}

/*
 * is_projection_capable_plan
 *		检查给定的计划节点是否能够进行投影。
 */
bool is_projection_capable_plan(Plan *fc_plan)
{
	/* 大多数计划类型都可以投影，因此只列出不能的那些 */
	switch (nodeTag(fc_plan))
	{
		case T_Hash:
		case T_Material:
		case T_Memoize:
		case T_Sort:
		case T_Unique:
		case T_SetOp:
		case T_LockRows:
		case T_Limit:
		case T_ModifyTable:
		case T_Append:
		case T_MergeAppend:
		case T_RecursiveUnion:
			return false;
		case T_CustomScan:
			if (((CustomScan *) fc_plan)->flags & CUSTOMPATH_SUPPORT_PROJECTION)
				return true;
			return false;
		case T_ProjectSet:

			/*
			 * 尽管 ProjectSet 当然可以投影，但我们还是说“不”，因为我们不希望规划器随机替换其目标列表为其他内容；SRF 必须保留在顶部。这可能在稍后放宽。
			 */
			return false;
		default:
			break;
	}
	return true;
}
